diff options
Diffstat (limited to 'net/core/link_watch.c')
| -rw-r--r-- | net/core/link_watch.c | 271 |
1 files changed, 190 insertions, 81 deletions
diff --git a/net/core/link_watch.c b/net/core/link_watch.c index d43d1201275..bd0767e6b2b 100644 --- a/net/core/link_watch.c +++ b/net/core/link_watch.c @@ -11,7 +11,6 @@ * */ -#include <linux/config.h> #include <linux/module.h> #include <linux/netdevice.h> #include <linux/if.h> @@ -20,125 +19,235 @@ #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> #include <asm/types.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 DEFINE_SPINLOCK(lweventlist_lock); -struct lw_event { - struct list_head list; - struct net_device *dev; -}; +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); -/* Avoid kmalloc() for most systems */ -static struct lw_event singleevent; + if (netif_dormant(dev)) + return IF_OPER_DORMANT; -/* Must be called with the rtnl semaphore held */ -void linkwatch_run_queue(void) + return IF_OPER_UP; +} + + +static void rfc2863_policy(struct net_device *dev) { - LIST_HEAD(head); - struct list_head *n, *next; + unsigned char operstate = default_operstate(dev); - spin_lock_irq(&lweventlist_lock); - list_splice_init(&lweventlist, &head); - spin_unlock_irq(&lweventlist_lock); + if (operstate == dev->operstate) + return; - list_for_each_safe(n, next, &head) { - struct lw_event *event = list_entry(n, struct lw_event, list); - struct net_device *dev = event->dev; + write_lock_bh(&dev_base_lock); - if (event == &singleevent) { - clear_bit(LW_SE_USED, &linkwatch_flags); - } else { - kfree(event); - } + switch(dev->link_mode) { + case IF_LINK_MODE_DORMANT: + if (operstate == IF_OPER_UP) + operstate = IF_OPER_DORMANT; + break; - /* We are about to handle this device, - * so new events can be accepted - */ - clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state); + case IF_LINK_MODE_DEFAULT: + default: + break; + } - if (dev->flags & IFF_UP) { - if (netif_carrier_ok(dev)) { - WARN_ON(dev->qdisc_sleeping == &noop_qdisc); - dev_activate(dev); - } else - dev_deactivate(dev); + dev->operstate = operstate; - netdev_state_change(dev); - } + write_unlock_bh(&dev_base_lock); +} - dev_put(dev); - } -} +void linkwatch_init_dev(struct net_device *dev) +{ + /* Handle pre-registration link state changes */ + if (!netif_carrier_ok(dev) || netif_dormant(dev)) + rfc2863_policy(dev); +} -static void linkwatch_event(void *dummy) + +static bool linkwatch_urgent_event(struct net_device *dev) { - /* 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); - - rtnl_shlock(); - linkwatch_run_queue(); - rtnl_shunlock(); + if (!netif_running(dev)) + return false; + + if (dev->ifindex != dev->iflink) + return true; + + if (dev->priv_flags & IFF_TEAM_PORT) + return true; + + return netif_carrier_ok(dev) && qdisc_tx_changing(dev); } -void linkwatch_fire_event(struct net_device *dev) +static void linkwatch_add_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; - } + unsigned long flags; + spin_lock_irqsave(&lweventlist_lock, flags); + if (list_empty(&dev->link_watch_list)) { + list_add_tail(&dev->link_watch_list, &lweventlist); dev_hold(dev); - event->dev = 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; + + /* + * If urgent, schedule immediate execution; otherwise, don't + * override the existing timer. + */ + if (test_bit(LW_URGENT, &linkwatch_flags)) + mod_delayed_work(system_wq, &linkwatch_work, 0); + else + schedule_delayed_work(&linkwatch_work, delay); +} + - spin_lock_irqsave(&lweventlist_lock, flags); - list_add_tail(&event->list, &lweventlist); - spin_unlock_irqrestore(&lweventlist_lock, flags); +static void linkwatch_do_dev(struct net_device *dev) +{ + /* + * Make sure the above read is complete since it can be + * rewritten as soon as we clear the bit below. + */ + smp_mb__before_atomic(); + + /* We are about to handle this device, + * so new events can be accepted + */ + clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state); + + rfc2863_policy(dev); + if (dev->flags & IFF_UP) { + if (netif_carrier_ok(dev)) + dev_activate(dev); + else + dev_deactivate(dev); + + netdev_state_change(dev); + } + dev_put(dev); +} + +static void __linkwatch_run_queue(int urgent_only) +{ + struct net_device *dev; + LIST_HEAD(wrk); - if (!test_and_set_bit(LW_RUNNING, &linkwatch_flags)) { - unsigned long thisevent = jiffies; + /* + * 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; - if (thisevent >= linkwatch_nextevent) { - schedule_work(&linkwatch_work); - } else { - schedule_delayed_work(&linkwatch_work, linkwatch_nextevent - thisevent); - } + clear_bit(LW_URGENT, &linkwatch_flags); + + spin_lock_irq(&lweventlist_lock); + list_splice_init(&lweventlist, &wrk); + + while (!list_empty(&wrk)) { + + dev = list_first_entry(&wrk, struct net_device, link_watch_list); + list_del_init(&dev->link_watch_list); + + if (urgent_only && !linkwatch_urgent_event(dev)) { + list_add_tail(&dev->link_watch_list, &lweventlist); + continue; } + spin_unlock_irq(&lweventlist_lock); + linkwatch_do_dev(dev); + spin_lock_irq(&lweventlist_lock); } + + if (!list_empty(&lweventlist)) + linkwatch_schedule_work(0); + spin_unlock_irq(&lweventlist_lock); } +void linkwatch_forget_dev(struct net_device *dev) +{ + unsigned long flags; + int clean = 0; + + spin_lock_irqsave(&lweventlist_lock, flags); + if (!list_empty(&dev->link_watch_list)) { + list_del_init(&dev->link_watch_list); + clean = 1; + } + spin_unlock_irqrestore(&lweventlist_lock, flags); + if (clean) + linkwatch_do_dev(dev); +} + + +/* Must be called with the rtnl semaphore held */ +void linkwatch_run_queue(void) +{ + __linkwatch_run_queue(0); +} + + +static void linkwatch_event(struct work_struct *dummy) +{ + rtnl_lock(); + __linkwatch_run_queue(time_after(linkwatch_nextevent, jiffies)); + rtnl_unlock(); +} + + +void linkwatch_fire_event(struct net_device *dev) +{ + bool urgent = linkwatch_urgent_event(dev); + + if (!test_and_set_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) { + linkwatch_add_event(dev); + } else if (!urgent) + return; + + linkwatch_schedule_work(urgent); +} EXPORT_SYMBOL(linkwatch_fire_event); |
