diff options
Diffstat (limited to 'net/core/link_watch.c')
| -rw-r--r-- | net/core/link_watch.c | 166 | 
1 files changed, 107 insertions, 59 deletions
diff --git a/net/core/link_watch.c b/net/core/link_watch.c index e3c26a9ccad..a5e372b9ec4 100644 --- a/net/core/link_watch.c +++ b/net/core/link_watch.c @@ -19,7 +19,6 @@  #include <linux/rtnetlink.h>  #include <linux/jiffies.h>  #include <linux/spinlock.h> -#include <linux/list.h>  #include <linux/slab.h>  #include <linux/workqueue.h>  #include <linux/bitops.h> @@ -27,8 +26,7 @@  enum lw_bits { -	LW_RUNNING = 0, -	LW_SE_USED +	LW_URGENT = 0,  };  static unsigned long linkwatch_flags; @@ -37,17 +35,9 @@ static unsigned long linkwatch_nextevent;  static void linkwatch_event(struct work_struct *dummy);  static DECLARE_DELAYED_WORK(linkwatch_work, linkwatch_event); -static LIST_HEAD(lweventlist); +static struct net_device *lweventlist;  static DEFINE_SPINLOCK(lweventlist_lock); -struct lw_event { -	struct list_head list; -	struct net_device *dev; -}; - -/* Avoid kmalloc() for most systems */ -static struct lw_event singleevent; -  static unsigned char default_operstate(const struct net_device *dev)  {  	if (!netif_carrier_ok(dev)) @@ -87,25 +77,102 @@ static void rfc2863_policy(struct net_device *dev)  } -/* Must be called with the rtnl semaphore held */ -void linkwatch_run_queue(void) +static int linkwatch_urgent_event(struct net_device *dev)  { -	struct list_head head, *n, *next; +	return netif_running(dev) && netif_carrier_ok(dev) && +	       dev->qdisc != dev->qdisc_sleeping; +} + + +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); +} + + +static void linkwatch_schedule_work(int urgent) +{ +	unsigned long delay = linkwatch_nextevent - jiffies; + +	if (test_bit(LW_URGENT, &linkwatch_flags)) +		return; + +	/* Minimise down-time: drop delay for up event. */ +	if (urgent) { +		if (test_and_set_bit(LW_URGENT, &linkwatch_flags)) +			return; +		delay = 0; +	} + +	/* 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); +} + + +static void __linkwatch_run_queue(int urgent_only) +{ +	struct net_device *next; + +	/* +	 * 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; +	/* Limit wrap-around effect on delay. */ +	else if (time_after(linkwatch_nextevent, jiffies + HZ)) +		linkwatch_nextevent = jiffies; + +	clear_bit(LW_URGENT, &linkwatch_flags);  	spin_lock_irq(&lweventlist_lock); -	list_replace_init(&lweventlist, &head); +	next = lweventlist; +	lweventlist = NULL;  	spin_unlock_irq(&lweventlist_lock); -	list_for_each_safe(n, next, &head) { -		struct lw_event *event = list_entry(n, struct lw_event, list); -		struct net_device *dev = event->dev; +	while (next) { +		struct net_device *dev = next; -		if (event == &singleevent) { -			clear_bit(LW_SE_USED, &linkwatch_flags); -		} else { -			kfree(event); +		next = dev->link_watch_next; + +		if (urgent_only && !linkwatch_urgent_event(dev)) { +			linkwatch_add_event(dev); +			continue;  		} +		/* +		 * 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(); +  		/* We are about to handle this device,  		 * so new events can be accepted  		 */ @@ -124,58 +191,39 @@ void linkwatch_run_queue(void)  		dev_put(dev);  	} + +	if (lweventlist) +		linkwatch_schedule_work(0);  } -static void linkwatch_event(struct work_struct *dummy) +/* Must be called with the rtnl semaphore held */ +void linkwatch_run_queue(void)  { -	/* 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 -	 */ -	linkwatch_nextevent = jiffies + HZ; -	clear_bit(LW_RUNNING, &linkwatch_flags); +	__linkwatch_run_queue(0); +} + +static void linkwatch_event(struct work_struct *dummy) +{  	rtnl_lock(); -	linkwatch_run_queue(); +	__linkwatch_run_queue(time_after(linkwatch_nextevent, jiffies));  	rtnl_unlock();  }  void linkwatch_fire_event(struct net_device *dev)  { -	if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) { -		unsigned long flags; -		struct lw_event *event; - -		if (test_and_set_bit(LW_SE_USED, &linkwatch_flags)) { -			event = kmalloc(sizeof(struct lw_event), GFP_ATOMIC); - -			if (unlikely(event == NULL)) { -				clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state); -				return; -			} -		} else { -			event = &singleevent; -		} +	int urgent = linkwatch_urgent_event(dev); +	if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {  		dev_hold(dev); -		event->dev = dev; - -		spin_lock_irqsave(&lweventlist_lock, flags); -		list_add_tail(&event->list, &lweventlist); -		spin_unlock_irqrestore(&lweventlist_lock, flags); -		if (!test_and_set_bit(LW_RUNNING, &linkwatch_flags)) { -			unsigned long delay = linkwatch_nextevent - jiffies; +		linkwatch_add_event(dev); +	} else if (!urgent) +		return; -			/* If we wrap around we'll delay it by at most HZ. */ -			if (delay > HZ) -				delay = 0; -			schedule_delayed_work(&linkwatch_work, delay); -		} -	} +	linkwatch_schedule_work(urgent);  }  EXPORT_SYMBOL(linkwatch_fire_event);  | 
