net: Optimize hard_start_xmit() return checking
[safe/jmp/linux-2.6] / net / core / link_watch.c
index 0f37266..bf8f7af 100644 (file)
@@ -11,7 +11,6 @@
  *
  */
 
-#include <linux/config.h>
 #include <linux/module.h>
 #include <linux/netdevice.h>
 #include <linux/if.h>
@@ -20,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>
 
 
 enum lw_bits {
-       LW_RUNNING = 0,
-       LW_SE_USED
+       LW_URGENT = 0,
 };
 
 static unsigned long linkwatch_flags;
 static unsigned long linkwatch_nextevent;
 
-static void linkwatch_event(void *dummy);
-static DECLARE_WORK(linkwatch_work, linkwatch_event, NULL);
+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))
@@ -80,7 +69,7 @@ static void rfc2863_policy(struct net_device *dev)
        case IF_LINK_MODE_DEFAULT:
        default:
                break;
-       };
+       }
 
        dev->operstate = operstate;
 
@@ -88,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 bool linkwatch_urgent_event(struct net_device *dev)
+{
+       return netif_running(dev) && netif_carrier_ok(dev) &&
+               qdisc_tx_changing(dev);
+}
+
+
+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 list_head head, *n, *next;
+       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
                 */
@@ -114,10 +180,9 @@ void linkwatch_run_queue(void)
 
                rfc2863_policy(dev);
                if (dev->flags & IFF_UP) {
-                       if (netif_carrier_ok(dev)) {
-                               WARN_ON(dev->qdisc_sleeping == &noop_qdisc);
+                       if (netif_carrier_ok(dev))
                                dev_activate(dev);
-                       else
+                       else
                                dev_deactivate(dev);
 
                        netdev_state_change(dev);
@@ -125,59 +190,39 @@ void linkwatch_run_queue(void)
 
                dev_put(dev);
        }
-}       
+
+       if (lweventlist)
+               linkwatch_schedule_work(0);
+}
 
 
-static void linkwatch_event(void *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;
-               }
+       bool 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 || delay > HZ)
-                               schedule_work(&linkwatch_work);
-                       else
-                               schedule_delayed_work(&linkwatch_work, delay);
-               }
-       }
+       linkwatch_schedule_work(urgent);
 }
 
 EXPORT_SYMBOL(linkwatch_fire_event);