From fb214965c60bdc7f7ff139356e50916bdabda9be Mon Sep 17 00:00:00 2001
From: Jesse Gross <jesse@nicira.com>
Date: Tue, 2 Feb 2010 18:43:55 -0500
Subject: [PATCH] gre: Optimize multiple unregistration.

Ports commit eef6dd "gre: Optimize multiple unregistration" from
the mainline kernel.
---
 datapath/linux-2.6/Modules.mk                 |  1 +
 datapath/linux-2.6/compat-2.6/dev-ip_gre.c    | 63 +++++++++++++++++++
 .../compat-2.6/include/linux/netdevice.h      |  6 ++
 datapath/linux-2.6/compat-2.6/ip_gre.c        | 15 +++--
 4 files changed, 80 insertions(+), 5 deletions(-)
 create mode 100644 datapath/linux-2.6/compat-2.6/dev-ip_gre.c

diff --git a/datapath/linux-2.6/Modules.mk b/datapath/linux-2.6/Modules.mk
index dafbcad77..c15b735f5 100644
--- a/datapath/linux-2.6/Modules.mk
+++ b/datapath/linux-2.6/Modules.mk
@@ -51,6 +51,7 @@ veth_headers =
 dist_modules += ip_gre
 build_modules += $(if $(BUILD_GRE),ip_gre)
 ip_gre_sources = \
+	linux-2.6/compat-2.6/dev-ip_gre.c \
 	linux-2.6/compat-2.6/ip_gre.c \
 	linux-2.6/compat-2.6/ip_output-ip_gre.c \
 	linux-2.6/compat-2.6/net_namespace-ip_gre.c
diff --git a/datapath/linux-2.6/compat-2.6/dev-ip_gre.c b/datapath/linux-2.6/compat-2.6/dev-ip_gre.c
new file mode 100644
index 000000000..04d830e72
--- /dev/null
+++ b/datapath/linux-2.6/compat-2.6/dev-ip_gre.c
@@ -0,0 +1,63 @@
+#include <linux/version.h>
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+
+#include <linux/netdevice.h>
+#include <linux/rtnetlink.h>
+
+struct netdev_list {
+	struct list_head unreg_list;
+	struct net_device *dev;
+};
+
+/**
+ *	unregister_netdevice_queue - remove device from the kernel
+ *	@dev: device
+ *	@head: list
+
+ *	This function shuts down a device interface and removes it
+ *	from the kernel tables.
+ *	If head not NULL, device is queued to be unregistered later.
+ *
+ *	Callers must hold the rtnl semaphore.  You may want
+ *	unregister_netdev() instead of this.
+ */
+
+void unregister_netdevice_queue(struct net_device *dev, struct list_head *head)
+{
+	ASSERT_RTNL();
+
+	if (head) {
+		struct netdev_list *list_item = kmalloc(sizeof *list_item,
+							GFP_KERNEL);
+		/* If we can't queue it, probably better to try to destroy it
+		 * now.  Either could potentially be bad but this is probably
+		 * less likely to cause problems. */
+		if (!list_item) {
+			unregister_netdevice(dev);
+			return;
+		}
+
+		list_item->dev = dev;
+		list_add_tail(&list_item->unreg_list, head);
+	} else
+		unregister_netdevice(dev);
+}
+
+/**
+ *	unregister_netdevice_many - unregister many devices
+ *	@head: list of devices
+ *
+ */
+void unregister_netdevice_many(struct list_head *head)
+{
+	if (!list_empty(head)) {
+		struct netdev_list *list_item, *next;
+
+		list_for_each_entry_safe(list_item, next, head, unreg_list) {
+			unregister_netdevice(list_item->dev);
+			kfree(list_item);
+		}
+	}
+}
+
+#endif /* kernel < 2.6.33 */
diff --git a/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h b/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h
index 0a0e47abd..0cd91b9f4 100644
--- a/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h
+++ b/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h
@@ -67,4 +67,10 @@ typedef int netdev_tx_t;
 #define net_xmit_eval(e)       ((e) == NET_XMIT_CN? 0 : (e))
 #endif
 
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
+extern void unregister_netdevice_queue(struct net_device *dev,
+					struct list_head *head);
+extern void unregister_netdevice_many(struct list_head *head);
+#endif
+
 #endif
diff --git a/datapath/linux-2.6/compat-2.6/ip_gre.c b/datapath/linux-2.6/compat-2.6/ip_gre.c
index 8df555519..7b23d1aa3 100644
--- a/datapath/linux-2.6/compat-2.6/ip_gre.c
+++ b/datapath/linux-2.6/compat-2.6/ip_gre.c
@@ -1446,16 +1446,19 @@ static const struct net_protocol ipgre_protocol = {
 #endif
 };
 
-static void ipgre_destroy_tunnels(struct ipgre_net *ign)
+static void ipgre_destroy_tunnels(struct ipgre_net *ign, struct list_head *head)
 {
 	int prio;
 
 	for (prio = 0; prio < 4; prio++) {
 		int h;
 		for (h = 0; h < HASH_SIZE; h++) {
-			struct ip_tunnel *t;
-			while ((t = ign->tunnels[prio][h]) != NULL)
-				unregister_netdevice(t->dev);
+			struct ip_tunnel *t = ign->tunnels[prio][h];
+
+			while (t != NULL) {
+				unregister_netdevice_queue(t->dev, head);
+				t = t->next;
+			}
 		}
 	}
 }
@@ -1509,10 +1512,12 @@ err_alloc:
 static void ipgre_exit_net(struct net *net)
 {
 	struct ipgre_net *ign;
+	LIST_HEAD(list);
 
 	ign = net_generic(net, ipgre_net_id);
 	rtnl_lock();
-	ipgre_destroy_tunnels(ign);
+	ipgre_destroy_tunnels(ign, &list);
+	unregister_netdevice_many(&list);
 	rtnl_unlock();
 	kfree(ign);
 }
-- 
2.47.0