2005-04-17 02:20:36 +04:00
/*
* Spanning tree protocol ; generic parts
* Linux ethernet bridge
*
* Authors :
* Lennert Buytenhek < buytenh @ gnu . org >
*
* 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 .
*/
# include <linux/kernel.h>
2008-05-12 23:21:05 +04:00
# include <linux/rculist.h>
2014-11-28 16:34:20 +03:00
# include <net/switchdev.h>
2005-04-17 02:20:36 +04:00
# include "br_private.h"
# include "br_private_stp.h"
/* since time values in bpdu are in jiffies and then scaled (1/256)
2012-03-01 12:12:18 +04:00
* before sending , make sure that is at least one STP tick .
2005-04-17 02:20:36 +04:00
*/
2012-03-01 12:12:18 +04:00
# define MESSAGE_AGE_INCR ((HZ / 256) + 1)
2005-04-17 02:20:36 +04:00
2009-08-05 21:42:58 +04:00
static const char * const br_port_state_names [ ] = {
2007-02-09 17:24:35 +03:00
[ BR_STATE_DISABLED ] = " disabled " ,
2005-04-17 02:20:36 +04:00
[ BR_STATE_LISTENING ] = " listening " ,
2007-02-09 17:24:35 +03:00
[ BR_STATE_LEARNING ] = " learning " ,
[ BR_STATE_FORWARDING ] = " forwarding " ,
2005-04-17 02:20:36 +04:00
[ BR_STATE_BLOCKING ] = " blocking " ,
} ;
2014-10-01 03:13:19 +04:00
void br_set_state ( struct net_bridge_port * p , unsigned int state )
{
2015-05-10 19:47:51 +03:00
struct switchdev_attr attr = {
2015-12-15 18:03:35 +03:00
. orig_dev = p - > dev ,
2015-10-01 12:03:42 +03:00
. id = SWITCHDEV_ATTR_ID_PORT_STP_STATE ,
2015-10-14 20:40:50 +03:00
. flags = SWITCHDEV_F_DEFER ,
2015-05-13 21:16:50 +03:00
. u . stp_state = state ,
2015-05-10 19:47:51 +03:00
} ;
2014-11-28 16:34:20 +03:00
int err ;
2014-10-01 03:13:19 +04:00
p - > state = state ;
2015-05-10 19:47:51 +03:00
err = switchdev_port_attr_set ( p - > dev , & attr ) ;
2015-11-13 14:06:12 +03:00
if ( err & & err ! = - EOPNOTSUPP )
2014-11-28 16:34:20 +03:00
br_warn ( p - > br , " error setting offload STP state on port %u(%s) \n " ,
( unsigned int ) p - > port_no , p - > dev - > name ) ;
2016-02-16 18:09:51 +03:00
else
br_info ( p - > br , " port %u(%s) entered %s state \n " ,
( unsigned int ) p - > port_no , p - > dev - > name ,
br_port_state_names [ p - > state ] ) ;
2014-10-01 03:13:19 +04:00
}
2005-04-17 02:20:36 +04:00
/* called under bridge lock */
struct net_bridge_port * br_get_port ( struct net_bridge * br , u16 port_no )
{
struct net_bridge_port * p ;
list_for_each_entry_rcu ( p , & br - > port_list , list ) {
if ( p - > port_no = = port_no )
return p ;
}
return NULL ;
}
/* called under bridge lock */
2007-02-09 17:24:35 +03:00
static int br_should_become_root_port ( const struct net_bridge_port * p ,
2005-04-17 02:20:36 +04:00
u16 root_port )
{
struct net_bridge * br ;
struct net_bridge_port * rp ;
int t ;
br = p - > br ;
if ( p - > state = = BR_STATE_DISABLED | |
br_is_designated_port ( p ) )
return 0 ;
if ( memcmp ( & br - > bridge_id , & p - > designated_root , 8 ) < = 0 )
return 0 ;
if ( ! root_port )
return 1 ;
rp = br_get_port ( br , root_port ) ;
t = memcmp ( & p - > designated_root , & rp - > designated_root , 8 ) ;
if ( t < 0 )
return 1 ;
else if ( t > 0 )
return 0 ;
if ( p - > designated_cost + p - > path_cost <
rp - > designated_cost + rp - > path_cost )
return 1 ;
else if ( p - > designated_cost + p - > path_cost >
rp - > designated_cost + rp - > path_cost )
return 0 ;
t = memcmp ( & p - > designated_bridge , & rp - > designated_bridge , 8 ) ;
if ( t < 0 )
return 1 ;
else if ( t > 0 )
return 0 ;
if ( p - > designated_port < rp - > designated_port )
return 1 ;
else if ( p - > designated_port > rp - > designated_port )
return 0 ;
if ( p - > port_id < rp - > port_id )
return 1 ;
return 0 ;
}
2012-11-13 11:53:08 +04:00
static void br_root_port_block ( const struct net_bridge * br ,
struct net_bridge_port * p )
{
br_notice ( br , " port %u(%s) tried to become root port (blocked) " ,
( unsigned int ) p - > port_no , p - > dev - > name ) ;
2014-10-01 03:13:19 +04:00
br_set_state ( p , BR_STATE_LISTENING ) ;
2012-11-13 11:53:08 +04:00
br_ifinfo_notify ( RTM_NEWLINK , p ) ;
if ( br - > forward_delay > 0 )
mod_timer ( & p - > forward_delay_timer , jiffies + br - > forward_delay ) ;
}
2005-04-17 02:20:36 +04:00
/* called under bridge lock */
static void br_root_selection ( struct net_bridge * br )
{
struct net_bridge_port * p ;
u16 root_port = 0 ;
list_for_each_entry ( p , & br - > port_list , list ) {
2012-11-13 11:53:08 +04:00
if ( ! br_should_become_root_port ( p , root_port ) )
continue ;
if ( p - > flags & BR_ROOT_BLOCK )
br_root_port_block ( br , p ) ;
else
2005-04-17 02:20:36 +04:00
root_port = p - > port_no ;
}
br - > root_port = root_port ;
if ( ! root_port ) {
br - > designated_root = br - > bridge_id ;
br - > root_path_cost = 0 ;
} else {
p = br_get_port ( br , root_port ) ;
br - > designated_root = p - > designated_root ;
br - > root_path_cost = p - > designated_cost + p - > path_cost ;
}
}
/* called under bridge lock */
void br_become_root_bridge ( struct net_bridge * br )
{
br - > max_age = br - > bridge_max_age ;
br - > hello_time = br - > bridge_hello_time ;
br - > forward_delay = br - > bridge_forward_delay ;
br_topology_change_detection ( br ) ;
del_timer ( & br - > tcn_timer ) ;
if ( br - > dev - > flags & IFF_UP ) {
br_config_bpdu_generation ( br ) ;
mod_timer ( & br - > hello_timer , jiffies + br - > hello_time ) ;
}
}
/* called under bridge lock */
void br_transmit_config ( struct net_bridge_port * p )
{
struct br_config_bpdu bpdu ;
struct net_bridge * br ;
if ( timer_pending ( & p - > hold_timer ) ) {
p - > config_pending = 1 ;
return ;
}
br = p - > br ;
bpdu . topology_change = br - > topology_change ;
bpdu . topology_change_ack = p - > topology_change_ack ;
bpdu . root = br - > designated_root ;
bpdu . root_path_cost = br - > root_path_cost ;
bpdu . bridge_id = br - > bridge_id ;
bpdu . port_id = p - > port_id ;
if ( br_is_root_bridge ( br ) )
bpdu . message_age = 0 ;
else {
struct net_bridge_port * root
= br_get_port ( br , br - > root_port ) ;
2011-07-22 11:47:06 +04:00
bpdu . message_age = ( jiffies - root - > designated_age )
2005-04-17 02:20:36 +04:00
+ MESSAGE_AGE_INCR ;
}
bpdu . max_age = br - > max_age ;
bpdu . hello_time = br - > hello_time ;
bpdu . forward_delay = br - > forward_delay ;
if ( bpdu . message_age < br - > max_age ) {
br_send_config_bpdu ( p , & bpdu ) ;
p - > topology_change_ack = 0 ;
p - > config_pending = 0 ;
2015-07-23 21:01:05 +03:00
if ( p - > br - > stp_enabled = = BR_KERNEL_STP )
mod_timer ( & p - > hold_timer ,
round_jiffies ( jiffies + BR_HOLD_TIME ) ) ;
2005-04-17 02:20:36 +04:00
}
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
static void br_record_config_information ( struct net_bridge_port * p ,
const struct br_config_bpdu * bpdu )
2005-04-17 02:20:36 +04:00
{
p - > designated_root = bpdu - > root ;
p - > designated_cost = bpdu - > root_path_cost ;
p - > designated_bridge = bpdu - > bridge_id ;
p - > designated_port = bpdu - > port_id ;
2012-03-01 12:12:19 +04:00
p - > designated_age = jiffies - bpdu - > message_age ;
2005-04-17 02:20:36 +04:00
2007-02-09 17:24:35 +03:00
mod_timer ( & p - > message_age_timer , jiffies
2013-09-12 08:37:47 +04:00
+ ( bpdu - > max_age - bpdu - > message_age ) ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
static void br_record_config_timeout_values ( struct net_bridge * br ,
2005-04-17 02:20:36 +04:00
const struct br_config_bpdu * bpdu )
{
br - > max_age = bpdu - > max_age ;
br - > hello_time = bpdu - > hello_time ;
br - > forward_delay = bpdu - > forward_delay ;
2016-12-10 21:44:28 +03:00
__br_set_topology_change ( br , bpdu - > topology_change ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
void br_transmit_tcn ( struct net_bridge * br )
{
2013-04-30 09:29:27 +04:00
struct net_bridge_port * p ;
p = br_get_port ( br , br - > root_port ) ;
if ( p )
br_send_tcn_bpdu ( p ) ;
else
br_notice ( br , " root port %u not found for topology notice \n " ,
br - > root_port ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
static int br_should_become_designated_port ( const struct net_bridge_port * p )
{
struct net_bridge * br ;
int t ;
br = p - > br ;
if ( br_is_designated_port ( p ) )
return 1 ;
if ( memcmp ( & p - > designated_root , & br - > designated_root , 8 ) )
return 1 ;
if ( br - > root_path_cost < p - > designated_cost )
return 1 ;
else if ( br - > root_path_cost > p - > designated_cost )
return 0 ;
t = memcmp ( & br - > bridge_id , & p - > designated_bridge , 8 ) ;
if ( t < 0 )
return 1 ;
else if ( t > 0 )
return 0 ;
if ( p - > port_id < p - > designated_port )
return 1 ;
return 0 ;
}
/* called under bridge lock */
static void br_designated_port_selection ( struct net_bridge * br )
{
struct net_bridge_port * p ;
list_for_each_entry ( p , & br - > port_list , list ) {
if ( p - > state ! = BR_STATE_DISABLED & &
br_should_become_designated_port ( p ) )
br_become_designated_port ( p ) ;
}
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
static int br_supersedes_port_info ( const struct net_bridge_port * p ,
const struct br_config_bpdu * bpdu )
2005-04-17 02:20:36 +04:00
{
int t ;
t = memcmp ( & bpdu - > root , & p - > designated_root , 8 ) ;
if ( t < 0 )
return 1 ;
else if ( t > 0 )
return 0 ;
if ( bpdu - > root_path_cost < p - > designated_cost )
return 1 ;
else if ( bpdu - > root_path_cost > p - > designated_cost )
return 0 ;
t = memcmp ( & bpdu - > bridge_id , & p - > designated_bridge , 8 ) ;
if ( t < 0 )
return 1 ;
else if ( t > 0 )
return 0 ;
if ( memcmp ( & bpdu - > bridge_id , & p - > br - > bridge_id , 8 ) )
return 1 ;
if ( bpdu - > port_id < = p - > designated_port )
return 1 ;
return 0 ;
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
static void br_topology_change_acknowledged ( struct net_bridge * br )
2005-04-17 02:20:36 +04:00
{
br - > topology_change_detected = 0 ;
del_timer ( & br - > tcn_timer ) ;
}
/* called under bridge lock */
void br_topology_change_detection ( struct net_bridge * br )
{
int isroot = br_is_root_bridge ( br ) ;
2009-05-15 10:11:58 +04:00
if ( br - > stp_enabled ! = BR_KERNEL_STP )
return ;
2010-05-10 13:31:09 +04:00
br_info ( br , " topology change detected, %s \n " ,
2005-04-17 02:20:36 +04:00
isroot ? " propagating " : " sending tcn bpdu " ) ;
if ( isroot ) {
2016-12-10 21:44:28 +03:00
__br_set_topology_change ( br , 1 ) ;
2005-04-17 02:20:36 +04:00
mod_timer ( & br - > topology_change_timer , jiffies
+ br - > bridge_forward_delay + br - > bridge_max_age ) ;
} else if ( ! br - > topology_change_detected ) {
br_transmit_tcn ( br ) ;
mod_timer ( & br - > tcn_timer , jiffies + br - > bridge_hello_time ) ;
}
br - > topology_change_detected = 1 ;
}
/* called under bridge lock */
void br_config_bpdu_generation ( struct net_bridge * br )
{
struct net_bridge_port * p ;
list_for_each_entry ( p , & br - > port_list , list ) {
if ( p - > state ! = BR_STATE_DISABLED & &
br_is_designated_port ( p ) )
br_transmit_config ( p ) ;
}
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
static void br_reply ( struct net_bridge_port * p )
2005-04-17 02:20:36 +04:00
{
br_transmit_config ( p ) ;
}
/* called under bridge lock */
void br_configuration_update ( struct net_bridge * br )
{
br_root_selection ( br ) ;
br_designated_port_selection ( br ) ;
}
/* called under bridge lock */
void br_become_designated_port ( struct net_bridge_port * p )
{
struct net_bridge * br ;
br = p - > br ;
p - > designated_root = br - > designated_root ;
p - > designated_cost = br - > root_path_cost ;
p - > designated_bridge = br - > bridge_id ;
p - > designated_port = p - > port_id ;
}
/* called under bridge lock */
static void br_make_blocking ( struct net_bridge_port * p )
{
if ( p - > state ! = BR_STATE_DISABLED & &
p - > state ! = BR_STATE_BLOCKING ) {
if ( p - > state = = BR_STATE_FORWARDING | |
p - > state = = BR_STATE_LEARNING )
br_topology_change_detection ( p - > br ) ;
2014-10-01 03:13:19 +04:00
br_set_state ( p , BR_STATE_BLOCKING ) ;
2011-07-22 11:47:09 +04:00
br_ifinfo_notify ( RTM_NEWLINK , p ) ;
2005-04-17 02:20:36 +04:00
del_timer ( & p - > forward_delay_timer ) ;
}
}
/* called under bridge lock */
static void br_make_forwarding ( struct net_bridge_port * p )
{
2008-08-06 05:42:51 +04:00
struct net_bridge * br = p - > br ;
2007-03-22 00:22:44 +03:00
2008-08-06 05:42:51 +04:00
if ( p - > state ! = BR_STATE_BLOCKING )
return ;
2011-03-10 08:57:04 +03:00
if ( br - > stp_enabled = = BR_NO_STP | | br - > forward_delay = = 0 ) {
2014-10-01 03:13:19 +04:00
br_set_state ( p , BR_STATE_FORWARDING ) ;
2008-08-06 05:42:51 +04:00
br_topology_change_detection ( br ) ;
del_timer ( & p - > forward_delay_timer ) ;
2011-07-22 11:47:10 +04:00
} else if ( br - > stp_enabled = = BR_KERNEL_STP )
2014-10-01 03:13:19 +04:00
br_set_state ( p , BR_STATE_LISTENING ) ;
2008-08-06 05:42:51 +04:00
else
2014-10-01 03:13:19 +04:00
br_set_state ( p , BR_STATE_LEARNING ) ;
2008-08-06 05:42:51 +04:00
2011-07-22 11:47:09 +04:00
br_ifinfo_notify ( RTM_NEWLINK , p ) ;
2008-08-06 05:42:51 +04:00
if ( br - > forward_delay ! = 0 )
mod_timer ( & p - > forward_delay_timer , jiffies + br - > forward_delay ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
void br_port_state_selection ( struct net_bridge * br )
{
struct net_bridge_port * p ;
2011-03-07 11:34:06 +03:00
unsigned int liveports = 0 ;
2005-04-17 02:20:36 +04:00
list_for_each_entry ( p , & br - > port_list , list ) {
2011-03-07 11:34:06 +03:00
if ( p - > state = = BR_STATE_DISABLED )
continue ;
2011-11-25 04:16:37 +04:00
/* Don't change port states if userspace is handling STP */
if ( br - > stp_enabled ! = BR_USER_STP ) {
if ( p - > port_no = = br - > root_port ) {
p - > config_pending = 0 ;
p - > topology_change_ack = 0 ;
br_make_forwarding ( p ) ;
} else if ( br_is_designated_port ( p ) ) {
del_timer ( & p - > message_age_timer ) ;
br_make_forwarding ( p ) ;
} else {
p - > config_pending = 0 ;
p - > topology_change_ack = 0 ;
br_make_blocking ( p ) ;
}
2005-04-17 02:20:36 +04:00
}
2015-06-19 11:45:50 +03:00
if ( p - > state ! = BR_STATE_BLOCKING )
br_multicast_enable_port ( p ) ;
2015-06-23 14:47:44 +03:00
/* Multicast is not disabled for the port when it goes in
* blocking state because the timers will expire and stop by
* themselves without sending more queries .
*/
2011-03-07 11:34:06 +03:00
if ( p - > state = = BR_STATE_FORWARDING )
+ + liveports ;
2005-04-17 02:20:36 +04:00
}
2011-03-07 11:34:06 +03:00
if ( liveports = = 0 )
netif_carrier_off ( br - > dev ) ;
else
netif_carrier_on ( br - > dev ) ;
2005-04-17 02:20:36 +04:00
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
static void br_topology_change_acknowledge ( struct net_bridge_port * p )
2005-04-17 02:20:36 +04:00
{
p - > topology_change_ack = 1 ;
br_transmit_config ( p ) ;
}
/* called under bridge lock */
2011-07-22 11:47:10 +04:00
void br_received_config_bpdu ( struct net_bridge_port * p ,
const struct br_config_bpdu * bpdu )
2005-04-17 02:20:36 +04:00
{
struct net_bridge * br ;
int was_root ;
2007-02-09 17:24:35 +03:00
2005-04-17 02:20:36 +04:00
br = p - > br ;
was_root = br_is_root_bridge ( br ) ;
if ( br_supersedes_port_info ( p , bpdu ) ) {
br_record_config_information ( p , bpdu ) ;
br_configuration_update ( br ) ;
br_port_state_selection ( br ) ;
if ( ! br_is_root_bridge ( br ) & & was_root ) {
del_timer ( & br - > hello_timer ) ;
if ( br - > topology_change_detected ) {
del_timer ( & br - > topology_change_timer ) ;
br_transmit_tcn ( br ) ;
2007-02-09 17:24:35 +03:00
mod_timer ( & br - > tcn_timer ,
2005-04-17 02:20:36 +04:00
jiffies + br - > bridge_hello_time ) ;
}
}
if ( p - > port_no = = br - > root_port ) {
br_record_config_timeout_values ( br , bpdu ) ;
br_config_bpdu_generation ( br ) ;
if ( bpdu - > topology_change_ack )
br_topology_change_acknowledged ( br ) ;
}
2007-02-09 17:24:35 +03:00
} else if ( br_is_designated_port ( p ) ) {
br_reply ( p ) ;
2005-04-17 02:20:36 +04:00
}
}
/* called under bridge lock */
void br_received_tcn_bpdu ( struct net_bridge_port * p )
{
if ( br_is_designated_port ( p ) ) {
2010-05-10 13:31:09 +04:00
br_info ( p - > br , " port %u(%s) received tcn bpdu \n " ,
2012-04-15 09:58:06 +04:00
( unsigned int ) p - > port_no , p - > dev - > name ) ;
2005-04-17 02:20:36 +04:00
br_topology_change_detection ( p - > br ) ;
br_topology_change_acknowledge ( p ) ;
}
}
2011-04-04 18:03:33 +04:00
/* Change bridge STP parameter */
int br_set_hello_time ( struct net_bridge * br , unsigned long val )
{
unsigned long t = clock_t_to_jiffies ( val ) ;
if ( t < BR_MIN_HELLO_TIME | | t > BR_MAX_HELLO_TIME )
return - ERANGE ;
spin_lock_bh ( & br - > lock ) ;
br - > bridge_hello_time = t ;
if ( br_is_root_bridge ( br ) )
br - > hello_time = br - > bridge_hello_time ;
spin_unlock_bh ( & br - > lock ) ;
return 0 ;
}
int br_set_max_age ( struct net_bridge * br , unsigned long val )
{
unsigned long t = clock_t_to_jiffies ( val ) ;
if ( t < BR_MIN_MAX_AGE | | t > BR_MAX_MAX_AGE )
return - ERANGE ;
spin_lock_bh ( & br - > lock ) ;
br - > bridge_max_age = t ;
if ( br_is_root_bridge ( br ) )
br - > max_age = br - > bridge_max_age ;
spin_unlock_bh ( & br - > lock ) ;
return 0 ;
}
2016-12-10 21:44:27 +03:00
/* called under bridge lock */
int __set_ageing_time ( struct net_device * dev , unsigned long t )
{
struct switchdev_attr attr = {
. orig_dev = dev ,
. id = SWITCHDEV_ATTR_ID_BRIDGE_AGEING_TIME ,
. flags = SWITCHDEV_F_SKIP_EOPNOTSUPP | SWITCHDEV_F_DEFER ,
. u . ageing_time = jiffies_to_clock_t ( t ) ,
} ;
int err ;
err = switchdev_port_attr_set ( dev , & attr ) ;
if ( err & & err ! = - EOPNOTSUPP )
return err ;
return 0 ;
}
2016-03-08 23:59:35 +03:00
/* Set time interval that dynamic forwarding entries live
* For pure software bridge , allow values outside the 802.1
* standard specification for special cases :
* 0 - entry never ages ( all permanant )
* 1 - entry disappears ( no persistance )
*
* Offloaded switch entries maybe more restrictive
*/
2016-07-21 19:42:19 +03:00
int br_set_ageing_time ( struct net_bridge * br , clock_t ageing_time )
2015-10-09 05:23:19 +03:00
{
unsigned long t = clock_t_to_jiffies ( ageing_time ) ;
int err ;
2016-12-10 21:44:27 +03:00
err = __set_ageing_time ( br - > dev , t ) ;
if ( err )
2015-10-09 05:23:19 +03:00
return err ;
2016-12-10 21:44:29 +03:00
spin_lock_bh ( & br - > lock ) ;
br - > bridge_ageing_time = t ;
2015-10-09 05:23:19 +03:00
br - > ageing_time = t ;
2016-12-10 21:44:29 +03:00
spin_unlock_bh ( & br - > lock ) ;
2017-02-04 20:05:07 +03:00
mod_delayed_work ( system_long_wq , & br - > gc_work , 0 ) ;
2015-10-09 05:23:19 +03:00
return 0 ;
}
2016-12-10 21:44:28 +03:00
/* called under bridge lock */
void __br_set_topology_change ( struct net_bridge * br , unsigned char val )
{
2016-12-10 21:44:29 +03:00
unsigned long t ;
int err ;
if ( br - > stp_enabled = = BR_KERNEL_STP & & br - > topology_change ! = val ) {
/* On topology change, set the bridge ageing time to twice the
* forward delay . Otherwise , restore its default ageing time .
*/
if ( val ) {
t = 2 * br - > forward_delay ;
br_debug ( br , " decreasing ageing time to %lu \n " , t ) ;
} else {
t = br - > bridge_ageing_time ;
br_debug ( br , " restoring ageing time to %lu \n " , t ) ;
}
err = __set_ageing_time ( br - > dev , t ) ;
if ( err )
br_warn ( br , " error offloading ageing time \n " ) ;
else
br - > ageing_time = t ;
}
2016-12-10 21:44:28 +03:00
br - > topology_change = val ;
}
2013-09-12 11:12:05 +04:00
void __br_set_forward_delay ( struct net_bridge * br , unsigned long t )
{
br - > bridge_forward_delay = t ;
if ( br_is_root_bridge ( br ) )
br - > forward_delay = br - > bridge_forward_delay ;
}
2011-04-04 18:03:33 +04:00
int br_set_forward_delay ( struct net_bridge * br , unsigned long val )
{
unsigned long t = clock_t_to_jiffies ( val ) ;
2015-11-10 14:15:32 +03:00
int err = - ERANGE ;
2011-04-04 18:03:33 +04:00
2015-09-24 21:20:11 +03:00
spin_lock_bh ( & br - > lock ) ;
2015-11-10 14:15:32 +03:00
if ( br - > stp_enabled ! = BR_NO_STP & &
( t < BR_MIN_FORWARD_DELAY | | t > BR_MAX_FORWARD_DELAY ) )
goto unlock ;
2013-09-12 11:12:05 +04:00
__br_set_forward_delay ( br , t ) ;
2015-11-10 14:15:32 +03:00
err = 0 ;
unlock :
2011-04-04 18:03:33 +04:00
spin_unlock_bh ( & br - > lock ) ;
2015-11-10 14:15:32 +03:00
return err ;
2011-04-04 18:03:33 +04:00
}