Commit 63c937c9 authored by Stefan Rompf's avatar Stefan Rompf Committed by David S. Miller

[NET]: Add device linkwatch functionality.

parent 0397a5fb
...@@ -207,7 +207,8 @@ enum netdev_state_t ...@@ -207,7 +207,8 @@ enum netdev_state_t
__LINK_STATE_PRESENT, __LINK_STATE_PRESENT,
__LINK_STATE_SCHED, __LINK_STATE_SCHED,
__LINK_STATE_NOCARRIER, __LINK_STATE_NOCARRIER,
__LINK_STATE_RX_SCHED __LINK_STATE_RX_SCHED,
__LINK_STATE_LINKWATCH_PENDING
}; };
...@@ -631,6 +632,8 @@ static inline void dev_put(struct net_device *dev) ...@@ -631,6 +632,8 @@ static inline void dev_put(struct net_device *dev)
* who is responsible for serialization of these calls. * who is responsible for serialization of these calls.
*/ */
extern void linkwatch_fire_event(struct net_device *dev);
static inline int netif_carrier_ok(struct net_device *dev) static inline int netif_carrier_ok(struct net_device *dev)
{ {
return !test_bit(__LINK_STATE_NOCARRIER, &dev->state); return !test_bit(__LINK_STATE_NOCARRIER, &dev->state);
...@@ -640,14 +643,16 @@ extern void __netdev_watchdog_up(struct net_device *dev); ...@@ -640,14 +643,16 @@ extern void __netdev_watchdog_up(struct net_device *dev);
static inline void netif_carrier_on(struct net_device *dev) static inline void netif_carrier_on(struct net_device *dev)
{ {
clear_bit(__LINK_STATE_NOCARRIER, &dev->state); if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state))
linkwatch_fire_event(dev);
if (netif_running(dev)) if (netif_running(dev))
__netdev_watchdog_up(dev); __netdev_watchdog_up(dev);
} }
static inline void netif_carrier_off(struct net_device *dev) static inline void netif_carrier_off(struct net_device *dev)
{ {
set_bit(__LINK_STATE_NOCARRIER, &dev->state); if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state))
linkwatch_fire_event(dev);
} }
/* Hot-plugging. */ /* Hot-plugging. */
......
...@@ -12,7 +12,7 @@ endif ...@@ -12,7 +12,7 @@ endif
obj-$(CONFIG_FILTER) += filter.o obj-$(CONFIG_FILTER) += filter.o
obj-$(CONFIG_NET) += dev.o dev_mcast.o dst.o neighbour.o rtnetlink.o utils.o obj-$(CONFIG_NET) += dev.o dev_mcast.o dst.o neighbour.o rtnetlink.o utils.o link_watch.o
obj-$(CONFIG_NETFILTER) += netfilter.o obj-$(CONFIG_NETFILTER) += netfilter.o
obj-$(CONFIG_NET_DIVERT) += dv.o obj-$(CONFIG_NET_DIVERT) += dv.o
......
...@@ -264,6 +264,7 @@ void dev_add_pack(struct packet_type *pt) ...@@ -264,6 +264,7 @@ void dev_add_pack(struct packet_type *pt)
br_write_unlock_bh(BR_NETPROTO_LOCK); br_write_unlock_bh(BR_NETPROTO_LOCK);
} }
extern void linkwatch_run_queue(void);
/** /**
* dev_remove_pack - remove packet handler * dev_remove_pack - remove packet handler
...@@ -2712,6 +2713,15 @@ int unregister_netdevice(struct net_device *dev) ...@@ -2712,6 +2713,15 @@ int unregister_netdevice(struct net_device *dev)
/* Rebroadcast unregister notification */ /* Rebroadcast unregister notification */
notifier_call_chain(&netdev_chain, notifier_call_chain(&netdev_chain,
NETDEV_UNREGISTER, dev); NETDEV_UNREGISTER, dev);
if (test_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state)) {
/* We must not have linkwatch events pending
* on unregister. If this happens, we simply
* run the queue unscheduled, resulting in a
* noop for this device
*/
linkwatch_run_queue();
}
} }
current->state = TASK_INTERRUPTIBLE; current->state = TASK_INTERRUPTIBLE;
schedule_timeout(HZ / 4); schedule_timeout(HZ / 4);
......
/*
* Linux network device link state notifaction
*
* 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/workqueue.h>
#include <linux/config.h>
#include <linux/netdevice.h>
#include <linux/if.h>
#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 <asm/bitops.h>
#include <asm/types.h>
enum lw_bits {
LW_RUNNING = 0,
LW_SE_USED
};
static unsigned long linkwatch_flags = 0;
static unsigned long linkwatch_nextevent = 0;
static void linkwatch_event(void *dummy);
static DECLARE_WORK(linkwatch_work, linkwatch_event, NULL);
static LIST_HEAD(lweventlist);
static spinlock_t lweventlist_lock = SPIN_LOCK_UNLOCKED;
struct lw_event {
struct list_head list;
struct net_device *dev;
};
/* Avoid kmalloc() for most systems */
static struct lw_event singleevent;
/* Must be called with the rtnl semaphore held */
void linkwatch_run_queue(void) {
LIST_HEAD(head);
struct list_head *n, *next;
spin_lock_irq(&lweventlist_lock);
list_splice_init(&lweventlist, &head);
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;
if (event == &singleevent) {
clear_bit(LW_SE_USED, &linkwatch_flags);
} else {
kfree(event);
}
/* We are about to handle this device,
* so new events can be accepted
*/
clear_bit(__LINK_STATE_LINKWATCH_PENDING, &dev->state);
if (dev->flags & IFF_UP) {
netdev_state_change(dev);
}
dev_put(dev);
}
}
static void linkwatch_event(void *dummy)
{
/* 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_lock();
linkwatch_run_queue();
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;
}
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 thisevent = jiffies;
if (thisevent >= linkwatch_nextevent) {
schedule_work(&linkwatch_work);
} else {
schedule_delayed_work(&linkwatch_work, linkwatch_nextevent - thisevent);
}
}
}
}
...@@ -632,4 +632,6 @@ extern void wireless_send_event(struct net_device *dev, unsigned int cmd, union ...@@ -632,4 +632,6 @@ extern void wireless_send_event(struct net_device *dev, unsigned int cmd, union
EXPORT_SYMBOL(wireless_send_event); EXPORT_SYMBOL(wireless_send_event);
#endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */ #endif /* CONFIG_NET_RADIO || CONFIG_NET_PCMCIA_RADIO */
EXPORT_SYMBOL(linkwatch_fire_event);
#endif /* CONFIG_NET */ #endif /* CONFIG_NET */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment