datapath: Add loop detection for RT kernels.
authorJesse Gross <jesse@nicira.com>
Mon, 18 Oct 2010 22:30:20 +0000 (15:30 -0700)
committerJesse Gross <jesse@nicira.com>
Mon, 25 Oct 2010 20:40:51 +0000 (13:40 -0700)
Our normal loop detection requires disabling preemption while
packet processing takes place.  On RT kernels this isn't acceptable
and interacts badly with spinlocks, so we can't use it.  This
takes advantage of some extra space that is added to struct
task_struct on RT kernels (and the knowledge that we will always
have a valid task_struct) to store the loop counter for a given
thread.  Since we can't make these assumptions on non-RT kernels,
we continue to use the previous method of loop detection there.

Signed-off-by: Jesse Gross <jesse@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
datapath/Modules.mk
datapath/datapath.c
datapath/loop_counter.c [new file with mode: 0644]
datapath/loop_counter.h [new file with mode: 0644]

index b632297b848e849f9a48d63c6474468beb45250c..cbf65a666176c9e57772ccab3b3be8214e692df2 100644 (file)
@@ -16,6 +16,7 @@ openvswitch_sources = \
        dp_sysfs_dp.c \
        dp_sysfs_if.c \
        flow.c \
+       loop_counter.c \
        table.c \
        tunnel.c \
        vport.c \
@@ -32,6 +33,7 @@ openvswitch_headers = \
        datapath.h \
        dp_sysfs.h \
        flow.h \
+       loop_counter.h \
        odp-compat.h \
        table.h \
        tunnel.h \
index b41110dbcb3fd978db959f3104a48a7cc972869e..522d4ec100c71c9329c5cd889ae74537bbeb3f82 100644 (file)
@@ -47,6 +47,7 @@
 #include "datapath.h"
 #include "actions.h"
 #include "flow.h"
+#include "loop_counter.h"
 #include "odp-compat.h"
 #include "table.h"
 #include "vport-internal_dev.h"
@@ -69,23 +70,6 @@ EXPORT_SYMBOL(dp_ioctl_hook);
 static struct datapath *dps[ODP_MAX];
 static DEFINE_MUTEX(dp_mutex);
 
-/* We limit the number of times that we pass into dp_process_received_packet()
- * to avoid blowing out the stack in the event that we have a loop. */
-struct loop_counter {
-       int count;              /* Count. */
-       bool looping;           /* Loop detected? */
-};
-
-#define DP_MAX_LOOPS 5
-
-/* We use a separate counter for each CPU for both interrupt and non-interrupt
- * context in order to keep the limit deterministic for a given packet. */
-struct percpu_loop_counters {
-       struct loop_counter counters[2];
-};
-
-static DEFINE_PER_CPU(struct percpu_loop_counters, dp_loop_counters);
-
 static int new_dp_port(struct datapath *, struct odp_port *, int port_no);
 
 /* Must be called with rcu_read_lock or dp_mutex. */
@@ -526,14 +510,6 @@ out:
        return err;
 }
 
-static void suppress_loop(struct datapath *dp, struct sw_flow_actions *actions)
-{
-       if (net_ratelimit())
-               pr_warn("%s: flow looped %d times, dropping\n",
-                       dp_name(dp), DP_MAX_LOOPS);
-       actions->n_actions = 0;
-}
-
 /* Must be called with rcu_read_lock. */
 void dp_process_received_packet(struct dp_port *p, struct sk_buff *skb)
 {
@@ -581,11 +557,11 @@ void dp_process_received_packet(struct dp_port *p, struct sk_buff *skb)
        acts = rcu_dereference(OVS_CB(skb)->flow->sf_acts);
 
        /* Check whether we've looped too much. */
-       loop = &get_cpu_var(dp_loop_counters).counters[!!in_interrupt()];
-       if (unlikely(++loop->count > DP_MAX_LOOPS))
+       loop = loop_get_counter();
+       if (unlikely(++loop->count > MAX_LOOPS))
                loop->looping = true;
        if (unlikely(loop->looping)) {
-               suppress_loop(dp, acts);
+               loop_suppress(dp, acts);
                goto out_loop;
        }
 
@@ -596,13 +572,13 @@ void dp_process_received_packet(struct dp_port *p, struct sk_buff *skb)
 
        /* Check whether sub-actions looped too much. */
        if (unlikely(loop->looping))
-               suppress_loop(dp, acts);
+               loop_suppress(dp, acts);
 
 out_loop:
        /* Decrement loop counter. */
        if (!--loop->count)
                loop->looping = false;
-       put_cpu_var(dp_loop_counters);
+       loop_put_counter();
 
 out:
        /* Update datapath statistics. */
diff --git a/datapath/loop_counter.c b/datapath/loop_counter.c
new file mode 100644 (file)
index 0000000..fbfbdcf
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Distributed under the terms of the GNU GPL version 2.
+ * Copyright (c) 2010 Nicira Networks.
+ *
+ * Significant portions of this file may be copied from parts of the Linux
+ * kernel, by Linus Torvalds and others.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/hardirq.h>
+#include <linux/kernel.h>
+#include <linux/percpu.h>
+#include <linux/sched.h>
+
+#include "loop_counter.h"
+
+void loop_suppress(struct datapath *dp, struct sw_flow_actions *actions)
+{
+       if (net_ratelimit())
+               pr_warn("%s: flow looped %d times, dropping\n",
+                       dp_name(dp), MAX_LOOPS);
+       actions->n_actions = 0;
+}
+
+#ifndef CONFIG_PREEMPT_RT
+
+/* We use a separate counter for each CPU for both interrupt and non-interrupt
+ * context in order to keep the limit deterministic for a given packet.
+ */
+struct percpu_loop_counters {
+       struct loop_counter counters[2];
+};
+
+static DEFINE_PER_CPU(struct percpu_loop_counters, loop_counters);
+
+struct loop_counter *loop_get_counter(void)
+{
+       return &get_cpu_var(loop_counters).counters[!!in_interrupt()];
+}
+
+void loop_put_counter(void)
+{
+       put_cpu_var(loop_counters);
+}
+
+#else /* !CONFIG_PREEMPT_RT */
+
+struct loop_counter *loop_get_counter(void)
+{
+       WARN_ON(in_interrupt());
+
+       /* Only two bits of the extra_flags field in struct task_struct are
+        * used and it's an unsigned int.  We hijack the most significant bits
+        * to be our counter structure.  On RT kernels softirqs always run in
+        * process context so we are guaranteed to have a valid task_struct.
+        */
+
+#ifdef __LITTLE_ENDIAN
+       return (void *)(&current->extra_flags + 1) -
+               sizeof(struct loop_counter);
+#elif __BIG_ENDIAN
+       return (struct loop_counter *)&current->extra_flags;
+#else
+#error "Please fix <asm/byteorder.h>."
+#endif
+}
+
+void loop_put_counter(void) { }
+
+#endif /* CONFIG_PREEMPT_RT */
diff --git a/datapath/loop_counter.h b/datapath/loop_counter.h
new file mode 100644 (file)
index 0000000..e08afb1
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2010 Nicira Networks.
+ * Distributed under the terms of the GNU GPL version 2.
+ *
+ * Significant portions of this file may be copied from parts of the Linux
+ * kernel, by Linus Torvalds and others.
+ */
+
+#ifndef LOOP_COUNTER_H
+#define LOOP_COUNTER_H 1
+
+#include "datapath.h"
+#include "flow.h"
+
+/* We limit the number of times that we pass into dp_process_received_packet()
+ * to avoid blowing out the stack in the event that we have a loop. */
+#define MAX_LOOPS 5
+
+struct loop_counter {
+       u8 count;               /* Count. */
+       bool looping;           /* Loop detected? */
+};
+
+struct loop_counter *loop_get_counter(void);
+void loop_put_counter(void);
+void loop_suppress(struct datapath *, struct sw_flow_actions *);
+
+#endif /* loop_counter.h */