2005-04-16 15:20:36 -07:00
/*
* Linux network device link state notification
*
* Author :
* Stefan Rompf < sux @ loplof . de >
*
* 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/module.h>
# include <linux/netdevice.h>
# include <linux/if.h>
# include <net/sock.h>
2005-05-03 16:18:52 -07:00
# include <net/pkt_sched.h>
2005-04-16 15:20:36 -07:00
# include <linux/rtnetlink.h>
# include <linux/jiffies.h>
# include <linux/spinlock.h>
# include <linux/slab.h>
# include <linux/workqueue.h>
# include <linux/bitops.h>
# include <asm/types.h>
enum lw_bits {
2007-05-09 00:17:30 -07:00
LW_URGENT = 0 ,
2005-04-16 15:20:36 -07:00
} ;
static unsigned long linkwatch_flags ;
static unsigned long linkwatch_nextevent ;
2006-11-22 14:55:48 +00:00
static void linkwatch_event ( struct work_struct * dummy ) ;
static DECLARE_DELAYED_WORK ( linkwatch_work , linkwatch_event ) ;
2005-04-16 15:20:36 -07:00
2007-05-08 18:34:17 -07:00
static struct net_device * lweventlist ;
2005-04-16 15:20:36 -07:00
static DEFINE_SPINLOCK ( lweventlist_lock ) ;
2006-03-20 17:09:11 -08:00
static unsigned char default_operstate ( const struct net_device * dev )
{
if ( ! netif_carrier_ok ( dev ) )
return ( dev - > ifindex ! = dev - > iflink ?
IF_OPER_LOWERLAYERDOWN : IF_OPER_DOWN ) ;
if ( netif_dormant ( dev ) )
return IF_OPER_DORMANT ;
return IF_OPER_UP ;
}
static void rfc2863_policy ( struct net_device * dev )
{
unsigned char operstate = default_operstate ( dev ) ;
if ( operstate = = dev - > operstate )
return ;
write_lock_bh ( & dev_base_lock ) ;
switch ( dev - > link_mode ) {
case IF_LINK_MODE_DORMANT :
if ( operstate = = IF_OPER_UP )
operstate = IF_OPER_DORMANT ;
break ;
case IF_LINK_MODE_DEFAULT :
default :
break ;
2007-04-20 17:09:22 -07:00
}
2006-03-20 17:09:11 -08:00
dev - > operstate = operstate ;
write_unlock_bh ( & dev_base_lock ) ;
}
2008-07-08 23:01:06 -07:00
static bool linkwatch_urgent_event ( struct net_device * dev )
2007-05-08 18:36:28 -07:00
{
return netif_running ( dev ) & & netif_carrier_ok ( dev ) & &
2008-07-08 23:01:06 -07:00
qdisc_tx_changing ( dev ) ;
2007-05-08 18:36:28 -07:00
}
static void linkwatch_add_event ( struct net_device * dev )
{
unsigned long flags ;
spin_lock_irqsave ( & lweventlist_lock , flags ) ;
dev - > link_watch_next = lweventlist ;
lweventlist = dev ;
spin_unlock_irqrestore ( & lweventlist_lock , flags ) ;
}
2007-05-09 00:17:30 -07:00
static void linkwatch_schedule_work ( int urgent )
2007-05-08 18:36:28 -07:00
{
2007-05-09 00:17:30 -07:00
unsigned long delay = linkwatch_nextevent - jiffies ;
if ( test_bit ( LW_URGENT , & linkwatch_flags ) )
2007-05-08 18:36:28 -07:00
return ;
2007-05-09 00:17:30 -07:00
/* Minimise down-time: drop delay for up event. */
if ( urgent ) {
if ( test_and_set_bit ( LW_URGENT , & linkwatch_flags ) )
return ;
2007-05-08 18:36:28 -07:00
delay = 0 ;
2007-05-08 23:22:43 -07:00
}
2007-05-08 18:36:28 -07:00
2007-05-09 00:17:30 -07:00
/* If we wrap around we'll delay it by at most HZ. */
if ( delay > HZ )
delay = 0 ;
/*
* This is true if we ' ve scheduled it immeditately or if we don ' t
* need an immediate execution and it ' s already pending .
*/
if ( schedule_delayed_work ( & linkwatch_work , delay ) = = ! delay )
return ;
/* Don't bother if there is nothing urgent. */
if ( ! test_bit ( LW_URGENT , & linkwatch_flags ) )
return ;
/* It's already running which is good enough. */
if ( ! cancel_delayed_work ( & linkwatch_work ) )
return ;
/* Otherwise we reschedule it again for immediate exection. */
schedule_delayed_work ( & linkwatch_work , 0 ) ;
2007-05-08 18:36:28 -07:00
}
static void __linkwatch_run_queue ( int urgent_only )
2005-04-16 15:20:36 -07:00
{
2007-05-08 18:34:17 -07:00
struct net_device * next ;
2005-04-16 15:20:36 -07:00
2007-05-08 18:36:28 -07:00
/*
* Limit the number of linkwatch events to one
* per second so that a runaway driver does not
* cause a storm of messages on the netlink
* socket . This limit does not apply to up events
* while the device qdisc is down .
*/
if ( ! urgent_only )
linkwatch_nextevent = jiffies + HZ ;
2007-05-09 00:17:30 -07:00
/* Limit wrap-around effect on delay. */
else if ( time_after ( linkwatch_nextevent , jiffies + HZ ) )
linkwatch_nextevent = jiffies ;
clear_bit ( LW_URGENT , & linkwatch_flags ) ;
2007-05-08 18:36:28 -07:00
2005-04-16 15:20:36 -07:00
spin_lock_irq ( & lweventlist_lock ) ;
2007-05-08 18:34:17 -07:00
next = lweventlist ;
lweventlist = NULL ;
2005-04-16 15:20:36 -07:00
spin_unlock_irq ( & lweventlist_lock ) ;
2007-05-08 18:34:17 -07:00
while ( next ) {
struct net_device * dev = next ;
2005-04-16 15:20:36 -07:00
2007-05-08 18:34:17 -07:00
next = dev - > link_watch_next ;
2007-05-08 18:36:28 -07:00
if ( urgent_only & & ! linkwatch_urgent_event ( dev ) ) {
linkwatch_add_event ( dev ) ;
continue ;
}
2007-05-08 18:34:17 -07:00
/*
* Make sure the above read is complete since it can be
* rewritten as soon as we clear the bit below .
*/
smp_mb__before_clear_bit ( ) ;
2005-04-16 15:20:36 -07:00
/* We are about to handle this device,
* so new events can be accepted
*/
clear_bit ( __LINK_STATE_LINKWATCH_PENDING , & dev - > state ) ;
2006-03-20 17:09:11 -08:00
rfc2863_policy ( dev ) ;
2005-04-16 15:20:36 -07:00
if ( dev - > flags & IFF_UP ) {
2008-07-08 23:01:06 -07:00
if ( netif_carrier_ok ( dev ) )
2005-05-03 16:18:52 -07:00
dev_activate ( dev ) ;
2008-07-08 23:01:06 -07:00
else
2005-05-03 16:18:52 -07:00
dev_deactivate ( dev ) ;
2005-04-16 15:20:36 -07:00
netdev_state_change ( dev ) ;
}
dev_put ( dev ) ;
}
2007-05-08 18:36:28 -07:00
if ( lweventlist )
2007-05-09 00:17:30 -07:00
linkwatch_schedule_work ( 0 ) ;
2007-02-09 23:24:36 +09:00
}
2005-04-16 15:20:36 -07:00
2007-05-08 18:36:28 -07:00
/* Must be called with the rtnl semaphore held */
void linkwatch_run_queue ( void )
2005-04-16 15:20:36 -07:00
{
2007-05-08 18:36:28 -07:00
__linkwatch_run_queue ( 0 ) ;
}
2005-04-16 15:20:36 -07:00
2007-05-08 18:36:28 -07:00
static void linkwatch_event ( struct work_struct * dummy )
{
2006-03-20 22:23:58 -08:00
rtnl_lock ( ) ;
2007-05-08 18:36:28 -07:00
__linkwatch_run_queue ( time_after ( linkwatch_nextevent , jiffies ) ) ;
2006-03-20 22:23:58 -08:00
rtnl_unlock ( ) ;
2005-04-16 15:20:36 -07:00
}
void linkwatch_fire_event ( struct net_device * dev )
{
2008-07-08 23:01:06 -07:00
bool urgent = linkwatch_urgent_event ( dev ) ;
2005-04-16 15:20:36 -07:00
2007-05-09 00:17:30 -07:00
if ( ! test_and_set_bit ( __LINK_STATE_LINKWATCH_PENDING , & dev - > state ) ) {
2005-04-16 15:20:36 -07:00
dev_hold ( dev ) ;
2007-05-08 18:36:28 -07:00
linkwatch_add_event ( dev ) ;
2007-05-09 00:17:30 -07:00
} else if ( ! urgent )
return ;
2005-04-16 15:20:36 -07:00
2007-05-09 00:17:30 -07:00
linkwatch_schedule_work ( urgent ) ;
2005-04-16 15:20:36 -07:00
}
EXPORT_SYMBOL ( linkwatch_fire_event ) ;