Merge tag 'ftracetest-3.19' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt...
[firefly-linux-kernel-4.4.55.git] / kernel / trace / trace_functions.c
index ffd56351b5217ddf3ed9cd0657d3f186452b7860..fcd41a166405b3321bee4443bce3d3da6f9b5c9e 100644 (file)
@@ -26,8 +26,6 @@ function_trace_call(unsigned long ip, unsigned long parent_ip,
 static void
 function_stack_trace_call(unsigned long ip, unsigned long parent_ip,
                          struct ftrace_ops *op, struct pt_regs *pt_regs);
-static struct ftrace_ops trace_ops;
-static struct ftrace_ops trace_stack_ops;
 static struct tracer_flags func_flags;
 
 /* Our option */
@@ -83,28 +81,24 @@ void ftrace_destroy_function_files(struct trace_array *tr)
 
 static int function_trace_init(struct trace_array *tr)
 {
-       struct ftrace_ops *ops;
-
-       if (tr->flags & TRACE_ARRAY_FL_GLOBAL) {
-               /* There's only one global tr */
-               if (!trace_ops.private) {
-                       trace_ops.private = tr;
-                       trace_stack_ops.private = tr;
-               }
+       ftrace_func_t func;
 
-               if (func_flags.val & TRACE_FUNC_OPT_STACK)
-                       ops = &trace_stack_ops;
-               else
-                       ops = &trace_ops;
-               tr->ops = ops;
-       } else if (!tr->ops) {
-               /*
-                * Instance trace_arrays get their ops allocated
-                * at instance creation. Unless it failed
-                * the allocation.
-                */
+       /*
+        * Instance trace_arrays get their ops allocated
+        * at instance creation. Unless it failed
+        * the allocation.
+        */
+       if (!tr->ops)
                return -ENOMEM;
-       }
+
+       /* Currently only the global instance can do stack tracing */
+       if (tr->flags & TRACE_ARRAY_FL_GLOBAL &&
+           func_flags.val & TRACE_FUNC_OPT_STACK)
+               func = function_stack_trace_call;
+       else
+               func = function_trace_call;
+
+       ftrace_init_array_ops(tr, func);
 
        tr->trace_buffer.cpu = get_cpu();
        put_cpu();
@@ -118,6 +112,7 @@ static void function_trace_reset(struct trace_array *tr)
 {
        tracing_stop_function_trace(tr);
        tracing_stop_cmdline_record();
+       ftrace_reset_array_ops(tr);
 }
 
 static void function_trace_start(struct trace_array *tr)
@@ -199,18 +194,6 @@ function_stack_trace_call(unsigned long ip, unsigned long parent_ip,
        local_irq_restore(flags);
 }
 
-static struct ftrace_ops trace_ops __read_mostly =
-{
-       .func = function_trace_call,
-       .flags = FTRACE_OPS_FL_GLOBAL | FTRACE_OPS_FL_RECURSION_SAFE,
-};
-
-static struct ftrace_ops trace_stack_ops __read_mostly =
-{
-       .func = function_stack_trace_call,
-       .flags = FTRACE_OPS_FL_GLOBAL | FTRACE_OPS_FL_RECURSION_SAFE,
-};
-
 static struct tracer_opt func_opts[] = {
 #ifdef CONFIG_STACKTRACE
        { TRACER_OPT(func_stack_trace, TRACE_FUNC_OPT_STACK) },
@@ -248,10 +231,10 @@ func_set_flag(struct trace_array *tr, u32 old_flags, u32 bit, int set)
                unregister_ftrace_function(tr->ops);
 
                if (set) {
-                       tr->ops = &trace_stack_ops;
+                       tr->ops->func = function_stack_trace_call;
                        register_ftrace_function(tr->ops);
                } else {
-                       tr->ops = &trace_ops;
+                       tr->ops->func = function_trace_call;
                        register_ftrace_function(tr->ops);
                }
 
@@ -269,7 +252,6 @@ static struct tracer function_trace __tracer_data =
        .init           = function_trace_init,
        .reset          = function_trace_reset,
        .start          = function_trace_start,
-       .wait_pipe      = poll_wait_pipe,
        .flags          = &func_flags,
        .set_flag       = func_set_flag,
        .allow_instances = true,
@@ -279,37 +261,74 @@ static struct tracer function_trace __tracer_data =
 };
 
 #ifdef CONFIG_DYNAMIC_FTRACE
-static int update_count(void **data)
+static void update_traceon_count(void **data, bool on)
 {
-       unsigned long *count = (long *)data;
+       long *count = (long *)data;
+       long old_count = *count;
 
-       if (!*count)
-               return 0;
+       /*
+        * Tracing gets disabled (or enabled) once per count.
+        * This function can be called at the same time on multiple CPUs.
+        * It is fine if both disable (or enable) tracing, as disabling
+        * (or enabling) the second time doesn't do anything as the
+        * state of the tracer is already disabled (or enabled).
+        * What needs to be synchronized in this case is that the count
+        * only gets decremented once, even if the tracer is disabled
+        * (or enabled) twice, as the second one is really a nop.
+        *
+        * The memory barriers guarantee that we only decrement the
+        * counter once. First the count is read to a local variable
+        * and a read barrier is used to make sure that it is loaded
+        * before checking if the tracer is in the state we want.
+        * If the tracer is not in the state we want, then the count
+        * is guaranteed to be the old count.
+        *
+        * Next the tracer is set to the state we want (disabled or enabled)
+        * then a write memory barrier is used to make sure that
+        * the new state is visible before changing the counter by
+        * one minus the old counter. This guarantees that another CPU
+        * executing this code will see the new state before seeing
+        * the new counter value, and would not do anything if the new
+        * counter is seen.
+        *
+        * Note, there is no synchronization between this and a user
+        * setting the tracing_on file. But we currently don't care
+        * about that.
+        */
+       if (!old_count)
+               return;
 
-       if (*count != -1)
-               (*count)--;
+       /* Make sure we see count before checking tracing state */
+       smp_rmb();
 
-       return 1;
+       if (on == !!tracing_is_on())
+               return;
+
+       if (on)
+               tracing_on();
+       else
+               tracing_off();
+
+       /* unlimited? */
+       if (old_count == -1)
+               return;
+
+       /* Make sure tracing state is visible before updating count */
+       smp_wmb();
+
+       *count = old_count - 1;
 }
 
 static void
 ftrace_traceon_count(unsigned long ip, unsigned long parent_ip, void **data)
 {
-       if (tracing_is_on())
-               return;
-
-       if (update_count(data))
-               tracing_on();
+       update_traceon_count(data, 1);
 }
 
 static void
 ftrace_traceoff_count(unsigned long ip, unsigned long parent_ip, void **data)
 {
-       if (!tracing_is_on())
-               return;
-
-       if (update_count(data))
-               tracing_off();
+       update_traceon_count(data, 0);
 }
 
 static void
@@ -348,11 +367,49 @@ ftrace_stacktrace(unsigned long ip, unsigned long parent_ip, void **data)
 static void
 ftrace_stacktrace_count(unsigned long ip, unsigned long parent_ip, void **data)
 {
-       if (!tracing_is_on())
-               return;
+       long *count = (long *)data;
+       long old_count;
+       long new_count;
 
-       if (update_count(data))
-               trace_dump_stack(STACK_SKIP);
+       /*
+        * Stack traces should only execute the number of times the
+        * user specified in the counter.
+        */
+       do {
+
+               if (!tracing_is_on())
+                       return;
+
+               old_count = *count;
+
+               if (!old_count)
+                       return;
+
+               /* unlimited? */
+               if (old_count == -1) {
+                       trace_dump_stack(STACK_SKIP);
+                       return;
+               }
+
+               new_count = old_count - 1;
+               new_count = cmpxchg(count, old_count, new_count);
+               if (new_count == old_count)
+                       trace_dump_stack(STACK_SKIP);
+
+       } while (new_count != old_count);
+}
+
+static int update_count(void **data)
+{
+       unsigned long *count = (long *)data;
+
+       if (!*count)
+               return 0;
+
+       if (*count != -1)
+               (*count)--;
+
+       return 1;
 }
 
 static void
@@ -379,7 +436,7 @@ ftrace_probe_print(const char *name, struct seq_file *m,
        seq_printf(m, "%ps:%s", (void *)ip, name);
 
        if (count == -1)
-               seq_printf(m, ":unlimited\n");
+               seq_puts(m, ":unlimited\n");
        else
                seq_printf(m, ":count=%ld\n", count);