Subject: PATCH idea - netlink and link changes

From: Tim Hockin <[email-masked]>
Date: Fri, 11 Oct 2002 09:59:11 -0700

I'm not subscribed to netdev or linux-net, I was asked to forward this 
patch to these lists.  Please CC: me on any commentary.

Looking for feedback on this quickie patch.  This enables netlink to
deliver link-change events, as reported by drivers via
netif_carrier_{on,off}.

* We could use a different RTM than NEWLINK (RTM_LINKCHANGE?) if we care
* We could do notifier_call_chain(netdev_chain) with NETDEV_UP or
NETDEV_LINKCHANGE (new) if we care

comments?

-- 
Tim Hockin
Systems Software Engineer
Sun Microsystems, Linux Kernel Engineering
[email-masked]

===== drivers/net/net_init.c 1.9 vs edited =====
--- 1.9/drivers/net/net_init.c	Mon Feb  4 23:44:55 2002
+++ edited/drivers/net/net_init.c	Thu Oct 10 11:44:18 2002
@@ -93,6 +93,7 @@
 		dev->priv = (void *) (((long)(dev + 1) + 31) & ~31);
 
 	setup(dev);
+	INIT_WORK(&dev->link_work, NULL, NULL);
 	strcpy(dev->name, mask);
 
 	return dev;
@@ -163,6 +164,7 @@
 	 */
 	
 	setup(dev);
+	INIT_WORK(&dev->link_work, NULL, NULL);
 	
 	if (new_device) {
 		int err;
===== include/linux/netdevice.h 1.24 vs edited =====
--- 1.24/include/linux/netdevice.h	Fri Oct  4 19:17:35 2002
+++ edited/include/linux/netdevice.h	Thu Oct 10 14:56:23 2002
@@ -35,6 +35,7 @@
 
 #ifdef __KERNEL__
 #include <linux/config.h>
+#include <linux/workqueue.h>
 #ifdef CONFIG_NET_PROFILE
 #include <net/profile.h>
 #endif
@@ -437,6 +438,7 @@
 	/* this will get initialized at each interface type init routine */
 	struct divert_blk	*divert;
 #endif /* CONFIG_NET_DIVERT */
+	struct work_struct	link_work;
 };

@@ -637,17 +639,20 @@
 }
 
 extern void __netdev_watchdog_up(struct net_device *dev);
+extern void netdev_do_link_work(struct net_device *dev);
 
 static inline void netif_carrier_on(struct net_device *dev)
 {
 	clear_bit(__LINK_STATE_NOCARRIER, &dev->state);
 	if (netif_running(dev))
 		__netdev_watchdog_up(dev);
+	netdev_do_link_work(dev);
 }
 
 static inline void netif_carrier_off(struct net_device *dev)
 {
 	set_bit(__LINK_STATE_NOCARRIER, &dev->state);
+	netdev_do_link_work(dev);
 }
 
 /* Hot-plugging. */
===== include/linux/workqueue.h 1.3 vs edited =====
--- 1.3/include/linux/workqueue.h	Thu Oct  3 14:29:58 2002
+++ edited/include/linux/workqueue.h	Thu Oct 10 16:50:36 2002
@@ -7,6 +7,7 @@
 
 #include <linux/timer.h>
 #include <linux/linkage.h>
+#include <linux/spinlock.h>
 
 struct workqueue_struct;
 
@@ -17,12 +18,15 @@
 	void *data;
 	void *wq_data;
 	struct timer_list timer;
+	spinlock_t lock;
 };
 
 #define __WORK_INITIALIZER(n, f, d) {				\
         .entry	= { &(n).entry, &(n).entry },			\
+	.pending = 0,						\
 	.func = (f),						\
-	.data = (d) }
+	.data = (d),						\
+	.lock = SPIN_LOCK_UNLOCKED }
 
 #define DECLARE_WORK(n, f, d)					\
 	struct work_struct n = __WORK_INITIALIZER(n, f, d)
@@ -45,6 +49,7 @@
 		(_work)->pending = 0;				\
 		PREPARE_WORK((_work), (_func), (_data));	\
 		init_timer(&(_work)->timer);			\
+		spin_lock_init(&(_work)->lock);			\
 	} while (0)
 
 extern struct workqueue_struct *create_workqueue(const char *name);
@@ -56,10 +61,13 @@
 
 extern int FASTCALL(schedule_work(struct work_struct *work));
 extern int FASTCALL(schedule_delayed_work(struct work_struct *work, unsigned long delay));
+extern int FASTCALL(cancel_work(struct work_struct *work));
 extern void flush_scheduled_work(void);
 extern int current_is_keventd(void);
 
 extern void init_workqueues(void);
+
+#define work_pending(_work)	test_bit(0, &(_work)->pending)
 
 #endif
 
===== kernel/workqueue.c 1.3 vs edited =====
--- 1.3/kernel/workqueue.c	Thu Oct  3 05:37:56 2002
+++ edited/kernel/workqueue.c	Thu Oct 10 17:12:28 2002
@@ -140,10 +140,13 @@
 		void *data = work->data;
 
 		list_del_init(cwq->worklist.next);
-		spin_unlock_irqrestore(&cwq->lock, flags);
+		spin_lock(&work->lock);
+		spin_unlock(&cwq->lock);
 
 		BUG_ON(work->wq_data != cwq);
 		clear_bit(0, &work->pending);
+		spin_unlock_irqrestore(&work->lock, flags);
+		
 		f(data);
 
 		/*
@@ -354,6 +357,28 @@
 	flush_workqueue(keventd_wq);
 }
 
+int cancel_work(struct work_struct *work)
+{
+	unsigned long flags;
+	struct cpu_workqueue_struct *cwq;
+	int cancelled = 0;
+
+	/* this should be safe to read - it only races with queue_*work() */
+	cwq = work->wq_data;
+
+	spin_lock_irqsave(&cwq->lock, flags);
+	spin_lock(&work->lock);
+	if (work_pending(work)) {
+		list_del_init(&work->entry);
+		clear_bit(0, &work->pending);
+		cancelled = 1;
+	}
+	spin_unlock(&work->lock);
+	spin_unlock_irqrestore(&cwq->lock, flags);
+
+	return cancelled;
+}
+
 int current_is_keventd(void)
 {
 	struct cpu_workqueue_struct *cwq;
@@ -386,4 +411,5 @@
 EXPORT_SYMBOL(schedule_work);
 EXPORT_SYMBOL(schedule_delayed_work);
 EXPORT_SYMBOL(flush_scheduled_work);
+EXPORT_SYMBOL(cancel_work);
 
===== net/netsyms.c 1.29 vs edited =====
--- 1.29/net/netsyms.c	Fri Oct  4 19:17:35 2002
+++ edited/net/netsyms.c	Thu Oct 10 14:57:53 2002
@@ -471,6 +471,7 @@
 EXPORT_SYMBOL(register_netdevice);
 EXPORT_SYMBOL(unregister_netdevice);
 EXPORT_SYMBOL(netdev_state_change);
+EXPORT_SYMBOL(netdev_do_link_work);
 EXPORT_SYMBOL(dev_new_index);
 EXPORT_SYMBOL(dev_get_by_index);
 EXPORT_SYMBOL(__dev_get_by_index);
===== net/core/dev.c 1.41 vs edited =====
--- 1.41/net/core/dev.c	Mon Oct  7 05:31:06 2002
+++ edited/net/core/dev.c	Thu Oct 10 16:05:06 2002
@@ -629,6 +629,20 @@
 	}
 }
 
+static void do_link_work(void *cookie)
+{
+	struct net_device *dev = cookie;
+	rtnl_lock();
+	rtmsg_ifinfo(RTM_NEWLINK, dev, IFF_RUNNING);
+	rtnl_unlock();
+}
+
+void netdev_do_link_work(struct net_device *dev)
+{
+	cancel_work(&dev->link_work);
+	PREPARE_WORK(&dev->link_work, do_link_work, dev);
+	schedule_work(&dev->link_work);
+}
 
 #ifdef CONFIG_KMOD