diff -urN linux-2.4.23/drivers/net/tulip/timer.c linux-2.4.23-linkwatch/drivers/net/tulip/timer.c --- linux-2.4.23/drivers/net/tulip/timer.c Fri Jun 13 16:51:35 2003 +++ linux-2.4.23-linkwatch/drivers/net/tulip/timer.c Thu Dec 11 14:57:49 2003 @@ -137,10 +137,10 @@ medianame[mleaf->media & MEDIA_MASK]); if ((p[2] & 0x61) == 0x01) /* Bogus Znyx board. */ goto actually_mii; - /* netif_carrier_on(dev); */ + netif_carrier_on(dev); break; } - /* netif_carrier_off(dev); */ + netif_carrier_off(dev); if (tp->medialock) break; select_next_media: @@ -164,10 +164,11 @@ } case 1: case 3: /* 21140, 21142 MII */ actually_mii: - if (tulip_check_duplex(dev) < 0) - { /* netif_carrier_off(dev); */ } - else - { /* netif_carrier_on(dev); */ } + if (tulip_check_duplex(dev) < 0) { + netif_carrier_off(dev); + } else { + netif_carrier_on(dev); + } next_tick = 60*HZ; break; case 2: /* 21142 serial block has no link beat. */ diff -urN linux-2.4.23/include/linux/netdevice.h linux-2.4.23-linkwatch/include/linux/netdevice.h --- linux-2.4.23/include/linux/netdevice.h Fri Nov 28 19:26:21 2003 +++ linux-2.4.23-linkwatch/include/linux/netdevice.h Wed Dec 10 16:14:52 2003 @@ -219,7 +219,8 @@ __LINK_STATE_PRESENT, __LINK_STATE_SCHED, __LINK_STATE_NOCARRIER, - __LINK_STATE_RX_SCHED + __LINK_STATE_RX_SCHED, + __LINK_STATE_LINKWATCH_PENDING }; @@ -648,6 +649,7 @@ * and _off may be called from IRQ context, but it is caller * 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) { @@ -658,14 +660,16 @@ 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)) __netdev_watchdog_up(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. */ diff -urN linux-2.4.23/net/8021q/vlan.c linux-2.4.23-linkwatch/net/8021q/vlan.c --- linux-2.4.23/net/8021q/vlan.c Fri Nov 28 19:26:21 2003 +++ linux-2.4.23-linkwatch/net/8021q/vlan.c Wed Dec 10 16:14:52 2003 @@ -588,6 +588,21 @@ */ switch (event) { + case NETDEV_CHANGE: + for (i = 0; i < VLAN_GROUP_ARRAY_LEN; i++) { + vlandev = grp->vlan_devices[i]; + if (vlandev) { + if (netif_carrier_ok(vlandev) != netif_carrier_ok(dev)) { + if (netif_carrier_ok(dev)) { + netif_carrier_on(vlandev); + } else { + netif_carrier_off(vlandev); + } + } + } + } + break; + case NETDEV_CHANGEADDR: case NETDEV_GOING_DOWN: /* Ignore for now */ diff -urN linux-2.4.23/net/8021q/vlan_dev.c linux-2.4.23-linkwatch/net/8021q/vlan_dev.c --- linux-2.4.23/net/8021q/vlan_dev.c Fri Nov 28 19:26:21 2003 +++ linux-2.4.23-linkwatch/net/8021q/vlan_dev.c Wed Dec 10 16:14:52 2003 @@ -760,8 +760,18 @@ int vlan_dev_open(struct net_device *dev) { - if (!(VLAN_DEV_INFO(dev)->real_dev->flags & IFF_UP)) - return -ENETDOWN; + struct net_device *real_dev = VLAN_DEV_INFO(dev)->real_dev; + + if (!real_dev->flags & IFF_UP) + return -ENETDOWN; + + if (netif_carrier_ok(real_dev) != netif_carrier_ok(dev)) { + if (netif_carrier_ok(real_dev)) { + netif_carrier_on(dev); + } else { + netif_carrier_off(dev); + } + } return 0; } diff -urN linux-2.4.23/net/core/Makefile linux-2.4.23-linkwatch/net/core/Makefile --- linux-2.4.23/net/core/Makefile Fri Nov 28 19:26:21 2003 +++ linux-2.4.23-linkwatch/net/core/Makefile Wed Dec 10 16:17:19 2003 @@ -22,7 +22,7 @@ obj-$(CONFIG_FILTER) += filter.o obj-$(CONFIG_NET) += dev.o ethtool.o dev_mcast.o dst.o neighbour.o \ - rtnetlink.o utils.o + rtnetlink.o utils.o link_watch.o obj-$(CONFIG_NETFILTER) += netfilter.o obj-$(CONFIG_NET_DIVERT) += dv.o diff -urN linux-2.4.23/net/core/dev.c linux-2.4.23-linkwatch/net/core/dev.c --- linux-2.4.23/net/core/dev.c Fri Nov 28 19:26:21 2003 +++ linux-2.4.23-linkwatch/net/core/dev.c Wed Dec 10 16:14:52 2003 @@ -193,6 +193,8 @@ int netdev_fastroute_obstacles; #endif +extern void linkwatch_init(void); +extern void linkwatch_run_queue(void); /****************************************************************************************** @@ -2700,6 +2702,16 @@ /* Rebroadcast unregister notification */ notifier_call_chain(&netdev_chain, 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; schedule_timeout(HZ/4); current->state = TASK_RUNNING; @@ -2746,6 +2758,11 @@ #ifdef CONFIG_NET_DIVERT dv_init(); #endif /* CONFIG_NET_DIVERT */ + + /* + * Link Watch initialization. + */ + linkwatch_init(); /* * Initialise the packet receive queues. diff -urN linux-2.4.23/net/core/link_watch.c linux-2.4.23-linkwatch/net/core/link_watch.c --- linux-2.4.23/net/core/link_watch.c Thu Jan 1 01:00:00 1970 +++ linux-2.4.23-linkwatch/net/core/link_watch.c Wed Dec 10 16:14:52 2003 @@ -0,0 +1,147 @@ +/* + * Linux network device link state notification + * + * Author: + * Stefan Rompf + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 void linkwatch_timer(unsigned long dummy); + +static struct tq_struct linkwatch_tq; +static struct timer_list linkwatch_ti; + +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(); +} + + +static void linkwatch_timer(unsigned long dummy) { + (void)schedule_task(&linkwatch_tq); +} + + +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) { + (void)schedule_task(&linkwatch_tq); + } else { + linkwatch_ti.expires = linkwatch_nextevent; + add_timer(&linkwatch_ti); + } + } + } +} + + +void __init linkwatch_init(void) { + linkwatch_ti.function = linkwatch_timer; + init_timer(&linkwatch_ti); + INIT_TQUEUE(&linkwatch_tq, linkwatch_event, NULL); +} diff -urN linux-2.4.23/net/netsyms.c linux-2.4.23-linkwatch/net/netsyms.c --- linux-2.4.23/net/netsyms.c Fri Nov 28 19:26:21 2003 +++ linux-2.4.23-linkwatch/net/netsyms.c Wed Dec 10 16:14:52 2003 @@ -608,6 +608,8 @@ EXPORT_SYMBOL(softnet_data); +EXPORT_SYMBOL(linkwatch_fire_event); + #if defined(CONFIG_NET_RADIO) || defined(CONFIG_NET_PCMCIA_RADIO) #include EXPORT_SYMBOL(wireless_send_event);