Merge branch 'akpm' (patches from Andrew)
[platform/kernel/linux-rpi.git] / kernel / trace / ftrace.c
index 7aaef2b..007a3de 100644 (file)
@@ -119,14 +119,9 @@ struct ftrace_ops __rcu *ftrace_ops_list __read_mostly = &ftrace_list_end;
 ftrace_func_t ftrace_trace_function __read_mostly = ftrace_stub;
 struct ftrace_ops global_ops;
 
-#if ARCH_SUPPORTS_FTRACE_OPS
-static void ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
-                                struct ftrace_ops *op, struct ftrace_regs *fregs);
-#else
-/* See comment below, where ftrace_ops_list_func is defined */
-static void ftrace_ops_no_ops(unsigned long ip, unsigned long parent_ip);
-#define ftrace_ops_list_func ((ftrace_func_t)ftrace_ops_no_ops)
-#endif
+/* Defined by vmlinux.lds.h see the commment above arch_ftrace_ops_list_func for details */
+void ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
+                         struct ftrace_ops *op, struct ftrace_regs *fregs);
 
 static inline void ftrace_ops_init(struct ftrace_ops *ops)
 {
@@ -581,7 +576,7 @@ static void ftrace_profile_reset(struct ftrace_profile_stat *stat)
               FTRACE_PROFILE_HASH_SIZE * sizeof(struct hlist_head));
 }
 
-int ftrace_profile_pages_init(struct ftrace_profile_stat *stat)
+static int ftrace_profile_pages_init(struct ftrace_profile_stat *stat)
 {
        struct ftrace_profile_page *pg;
        int functions;
@@ -988,8 +983,9 @@ static __init void ftrace_profile_tracefs(struct dentry *d_tracer)
                }
        }
 
-       entry = tracefs_create_file("function_profile_enabled", 0644,
-                                   d_tracer, NULL, &ftrace_profile_fops);
+       entry = tracefs_create_file("function_profile_enabled",
+                                   TRACE_MODE_WRITE, d_tracer, NULL,
+                                   &ftrace_profile_fops);
        if (!entry)
                pr_warn("Could not create tracefs 'function_profile_enabled' entry\n");
 }
@@ -2394,6 +2390,39 @@ unsigned long ftrace_find_rec_direct(unsigned long ip)
        return entry->direct;
 }
 
+static struct ftrace_func_entry*
+ftrace_add_rec_direct(unsigned long ip, unsigned long addr,
+                     struct ftrace_hash **free_hash)
+{
+       struct ftrace_func_entry *entry;
+
+       if (ftrace_hash_empty(direct_functions) ||
+           direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
+               struct ftrace_hash *new_hash;
+               int size = ftrace_hash_empty(direct_functions) ? 0 :
+                       direct_functions->count + 1;
+
+               if (size < 32)
+                       size = 32;
+
+               new_hash = dup_hash(direct_functions, size);
+               if (!new_hash)
+                       return NULL;
+
+               *free_hash = direct_functions;
+               direct_functions = new_hash;
+       }
+
+       entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+       if (!entry)
+               return NULL;
+
+       entry->ip = ip;
+       entry->direct = addr;
+       __add_hash_entry(direct_functions, entry);
+       return entry;
+}
+
 static void call_direct_funcs(unsigned long ip, unsigned long pip,
                              struct ftrace_ops *ops, struct ftrace_regs *fregs)
 {
@@ -5110,39 +5139,16 @@ int register_ftrace_direct(unsigned long ip, unsigned long addr)
        }
 
        ret = -ENOMEM;
-       if (ftrace_hash_empty(direct_functions) ||
-           direct_functions->count > 2 * (1 << direct_functions->size_bits)) {
-               struct ftrace_hash *new_hash;
-               int size = ftrace_hash_empty(direct_functions) ? 0 :
-                       direct_functions->count + 1;
-
-               if (size < 32)
-                       size = 32;
-
-               new_hash = dup_hash(direct_functions, size);
-               if (!new_hash)
-                       goto out_unlock;
-
-               free_hash = direct_functions;
-               direct_functions = new_hash;
-       }
-
-       entry = kmalloc(sizeof(*entry), GFP_KERNEL);
-       if (!entry)
-               goto out_unlock;
-
        direct = ftrace_find_direct_func(addr);
        if (!direct) {
                direct = ftrace_alloc_direct_func(addr);
-               if (!direct) {
-                       kfree(entry);
+               if (!direct)
                        goto out_unlock;
-               }
        }
 
-       entry->ip = ip;
-       entry->direct = addr;
-       __add_hash_entry(direct_functions, entry);
+       entry = ftrace_add_rec_direct(ip, addr, &free_hash);
+       if (!entry)
+               goto out_unlock;
 
        ret = ftrace_set_filter_ip(&direct_ops, ip, 0, 0);
        if (ret)
@@ -5395,6 +5401,216 @@ int modify_ftrace_direct(unsigned long ip,
        return ret;
 }
 EXPORT_SYMBOL_GPL(modify_ftrace_direct);
+
+#define MULTI_FLAGS (FTRACE_OPS_FL_IPMODIFY | FTRACE_OPS_FL_DIRECT | \
+                    FTRACE_OPS_FL_SAVE_REGS)
+
+static int check_direct_multi(struct ftrace_ops *ops)
+{
+       if (!(ops->flags & FTRACE_OPS_FL_INITIALIZED))
+               return -EINVAL;
+       if ((ops->flags & MULTI_FLAGS) != MULTI_FLAGS)
+               return -EINVAL;
+       return 0;
+}
+
+static void remove_direct_functions_hash(struct ftrace_hash *hash, unsigned long addr)
+{
+       struct ftrace_func_entry *entry, *del;
+       int size, i;
+
+       size = 1 << hash->size_bits;
+       for (i = 0; i < size; i++) {
+               hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+                       del = __ftrace_lookup_ip(direct_functions, entry->ip);
+                       if (del && del->direct == addr) {
+                               remove_hash_entry(direct_functions, del);
+                               kfree(del);
+                       }
+               }
+       }
+}
+
+/**
+ * register_ftrace_direct_multi - Call a custom trampoline directly
+ * for multiple functions registered in @ops
+ * @ops: The address of the struct ftrace_ops object
+ * @addr: The address of the trampoline to call at @ops functions
+ *
+ * This is used to connect a direct calls to @addr from the nop locations
+ * of the functions registered in @ops (with by ftrace_set_filter_ip
+ * function).
+ *
+ * The location that it calls (@addr) must be able to handle a direct call,
+ * and save the parameters of the function being traced, and restore them
+ * (or inject new ones if needed), before returning.
+ *
+ * Returns:
+ *  0 on success
+ *  -EINVAL  - The @ops object was already registered with this call or
+ *             when there are no functions in @ops object.
+ *  -EBUSY   - Another direct function is already attached (there can be only one)
+ *  -ENODEV  - @ip does not point to a ftrace nop location (or not supported)
+ *  -ENOMEM  - There was an allocation failure.
+ */
+int register_ftrace_direct_multi(struct ftrace_ops *ops, unsigned long addr)
+{
+       struct ftrace_hash *hash, *free_hash = NULL;
+       struct ftrace_func_entry *entry, *new;
+       int err = -EBUSY, size, i;
+
+       if (ops->func || ops->trampoline)
+               return -EINVAL;
+       if (!(ops->flags & FTRACE_OPS_FL_INITIALIZED))
+               return -EINVAL;
+       if (ops->flags & FTRACE_OPS_FL_ENABLED)
+               return -EINVAL;
+
+       hash = ops->func_hash->filter_hash;
+       if (ftrace_hash_empty(hash))
+               return -EINVAL;
+
+       mutex_lock(&direct_mutex);
+
+       /* Make sure requested entries are not already registered.. */
+       size = 1 << hash->size_bits;
+       for (i = 0; i < size; i++) {
+               hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+                       if (ftrace_find_rec_direct(entry->ip))
+                               goto out_unlock;
+               }
+       }
+
+       /* ... and insert them to direct_functions hash. */
+       err = -ENOMEM;
+       for (i = 0; i < size; i++) {
+               hlist_for_each_entry(entry, &hash->buckets[i], hlist) {
+                       new = ftrace_add_rec_direct(entry->ip, addr, &free_hash);
+                       if (!new)
+                               goto out_remove;
+                       entry->direct = addr;
+               }
+       }
+
+       ops->func = call_direct_funcs;
+       ops->flags = MULTI_FLAGS;
+       ops->trampoline = FTRACE_REGS_ADDR;
+
+       err = register_ftrace_function(ops);
+
+ out_remove:
+       if (err)
+               remove_direct_functions_hash(hash, addr);
+
+ out_unlock:
+       mutex_unlock(&direct_mutex);
+
+       if (free_hash) {
+               synchronize_rcu_tasks();
+               free_ftrace_hash(free_hash);
+       }
+       return err;
+}
+EXPORT_SYMBOL_GPL(register_ftrace_direct_multi);
+
+/**
+ * unregister_ftrace_direct_multi - Remove calls to custom trampoline
+ * previously registered by register_ftrace_direct_multi for @ops object.
+ * @ops: The address of the struct ftrace_ops object
+ *
+ * This is used to remove a direct calls to @addr from the nop locations
+ * of the functions registered in @ops (with by ftrace_set_filter_ip
+ * function).
+ *
+ * Returns:
+ *  0 on success
+ *  -EINVAL - The @ops object was not properly registered.
+ */
+int unregister_ftrace_direct_multi(struct ftrace_ops *ops, unsigned long addr)
+{
+       struct ftrace_hash *hash = ops->func_hash->filter_hash;
+       int err;
+
+       if (check_direct_multi(ops))
+               return -EINVAL;
+       if (!(ops->flags & FTRACE_OPS_FL_ENABLED))
+               return -EINVAL;
+
+       mutex_lock(&direct_mutex);
+       err = unregister_ftrace_function(ops);
+       remove_direct_functions_hash(hash, addr);
+       mutex_unlock(&direct_mutex);
+       return err;
+}
+EXPORT_SYMBOL_GPL(unregister_ftrace_direct_multi);
+
+/**
+ * modify_ftrace_direct_multi - Modify an existing direct 'multi' call
+ * to call something else
+ * @ops: The address of the struct ftrace_ops object
+ * @addr: The address of the new trampoline to call at @ops functions
+ *
+ * This is used to unregister currently registered direct caller and
+ * register new one @addr on functions registered in @ops object.
+ *
+ * Note there's window between ftrace_shutdown and ftrace_startup calls
+ * where there will be no callbacks called.
+ *
+ * Returns: zero on success. Non zero on error, which includes:
+ *  -EINVAL - The @ops object was not properly registered.
+ */
+int modify_ftrace_direct_multi(struct ftrace_ops *ops, unsigned long addr)
+{
+       struct ftrace_hash *hash;
+       struct ftrace_func_entry *entry, *iter;
+       static struct ftrace_ops tmp_ops = {
+               .func           = ftrace_stub,
+               .flags          = FTRACE_OPS_FL_STUB,
+       };
+       int i, size;
+       int err;
+
+       if (check_direct_multi(ops))
+               return -EINVAL;
+       if (!(ops->flags & FTRACE_OPS_FL_ENABLED))
+               return -EINVAL;
+
+       mutex_lock(&direct_mutex);
+
+       /* Enable the tmp_ops to have the same functions as the direct ops */
+       ftrace_ops_init(&tmp_ops);
+       tmp_ops.func_hash = ops->func_hash;
+
+       err = register_ftrace_function(&tmp_ops);
+       if (err)
+               goto out_direct;
+
+       /*
+        * Now the ftrace_ops_list_func() is called to do the direct callers.
+        * We can safely change the direct functions attached to each entry.
+        */
+       mutex_lock(&ftrace_lock);
+
+       hash = ops->func_hash->filter_hash;
+       size = 1 << hash->size_bits;
+       for (i = 0; i < size; i++) {
+               hlist_for_each_entry(iter, &hash->buckets[i], hlist) {
+                       entry = __ftrace_lookup_ip(direct_functions, iter->ip);
+                       if (!entry)
+                               continue;
+                       entry->direct = addr;
+               }
+       }
+
+       /* Removing the tmp_ops will add the updated direct callers to the functions */
+       unregister_ftrace_function(&tmp_ops);
+
+       mutex_unlock(&ftrace_lock);
+ out_direct:
+       mutex_unlock(&direct_mutex);
+       return err;
+}
+EXPORT_SYMBOL_GPL(modify_ftrace_direct_multi);
 #endif /* CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS */
 
 /**
@@ -6109,10 +6325,10 @@ void ftrace_create_filter_files(struct ftrace_ops *ops,
                                struct dentry *parent)
 {
 
-       trace_create_file("set_ftrace_filter", 0644, parent,
+       trace_create_file("set_ftrace_filter", TRACE_MODE_WRITE, parent,
                          ops, &ftrace_filter_fops);
 
-       trace_create_file("set_ftrace_notrace", 0644, parent,
+       trace_create_file("set_ftrace_notrace", TRACE_MODE_WRITE, parent,
                          ops, &ftrace_notrace_fops);
 }
 
@@ -6139,19 +6355,19 @@ void ftrace_destroy_filter_files(struct ftrace_ops *ops)
 static __init int ftrace_init_dyn_tracefs(struct dentry *d_tracer)
 {
 
-       trace_create_file("available_filter_functions", 0444,
+       trace_create_file("available_filter_functions", TRACE_MODE_READ,
                        d_tracer, NULL, &ftrace_avail_fops);
 
-       trace_create_file("enabled_functions", 0444,
+       trace_create_file("enabled_functions", TRACE_MODE_READ,
                        d_tracer, NULL, &ftrace_enabled_fops);
 
        ftrace_create_filter_files(&global_ops, d_tracer);
 
 #ifdef CONFIG_FUNCTION_GRAPH_TRACER
-       trace_create_file("set_graph_function", 0644, d_tracer,
+       trace_create_file("set_graph_function", TRACE_MODE_WRITE, d_tracer,
                                    NULL,
                                    &ftrace_graph_fops);
-       trace_create_file("set_graph_notrace", 0644, d_tracer,
+       trace_create_file("set_graph_notrace", TRACE_MODE_WRITE, d_tracer,
                                    NULL,
                                    &ftrace_graph_notrace_fops);
 #endif /* CONFIG_FUNCTION_GRAPH_TRACER */
@@ -6846,6 +7062,11 @@ void __init ftrace_free_init_mem(void)
        ftrace_free_mem(NULL, start, end);
 }
 
+int __init __weak ftrace_dyn_arch_init(void)
+{
+       return 0;
+}
+
 void __init ftrace_init(void)
 {
        extern unsigned long __start_mcount_loc[];
@@ -6977,16 +7198,15 @@ __ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
        struct ftrace_ops *op;
        int bit;
 
+       /*
+        * The ftrace_test_and_set_recursion() will disable preemption,
+        * which is required since some of the ops may be dynamically
+        * allocated, they must be freed after a synchronize_rcu().
+        */
        bit = trace_test_and_set_recursion(ip, parent_ip, TRACE_LIST_START);
        if (bit < 0)
                return;
 
-       /*
-        * Some of the ops may be dynamically allocated,
-        * they must be freed after a synchronize_rcu().
-        */
-       preempt_disable_notrace();
-
        do_for_each_ftrace_op(op, ftrace_ops_list) {
                /* Stub functions don't need to be called nor tested */
                if (op->flags & FTRACE_OPS_FL_STUB)
@@ -7010,7 +7230,6 @@ __ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
                }
        } while_for_each_ftrace_op(op);
 out:
-       preempt_enable_notrace();
        trace_clear_recursion(bit);
 }
 
@@ -7026,21 +7245,23 @@ out:
  * Note, CONFIG_DYNAMIC_FTRACE_WITH_REGS expects a full regs to be saved.
  * An architecture can pass partial regs with ftrace_ops and still
  * set the ARCH_SUPPORTS_FTRACE_OPS.
+ *
+ * In vmlinux.lds.h, ftrace_ops_list_func() is defined to be
+ * arch_ftrace_ops_list_func.
  */
 #if ARCH_SUPPORTS_FTRACE_OPS
-static void ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
-                                struct ftrace_ops *op, struct ftrace_regs *fregs)
+void arch_ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip,
+                              struct ftrace_ops *op, struct ftrace_regs *fregs)
 {
        __ftrace_ops_list_func(ip, parent_ip, NULL, fregs);
 }
-NOKPROBE_SYMBOL(ftrace_ops_list_func);
 #else
-static void ftrace_ops_no_ops(unsigned long ip, unsigned long parent_ip)
+void arch_ftrace_ops_list_func(unsigned long ip, unsigned long parent_ip)
 {
        __ftrace_ops_list_func(ip, parent_ip, NULL, NULL);
 }
-NOKPROBE_SYMBOL(ftrace_ops_no_ops);
 #endif
+NOKPROBE_SYMBOL(arch_ftrace_ops_list_func);
 
 /*
  * If there's only one function registered but it does not support
@@ -7056,12 +7277,9 @@ static void ftrace_ops_assist_func(unsigned long ip, unsigned long parent_ip,
        if (bit < 0)
                return;
 
-       preempt_disable_notrace();
-
        if (!(op->flags & FTRACE_OPS_FL_RCU) || rcu_is_watching())
                op->func(ip, parent_ip, op, fregs);
 
-       preempt_enable_notrace();
        trace_clear_recursion(bit);
 }
 NOKPROBE_SYMBOL(ftrace_ops_assist_func);
@@ -7184,10 +7402,10 @@ static void clear_ftrace_pids(struct trace_array *tr, int type)
        synchronize_rcu();
 
        if ((type & TRACE_PIDS) && pid_list)
-               trace_free_pid_list(pid_list);
+               trace_pid_list_free(pid_list);
 
        if ((type & TRACE_NO_PIDS) && no_pid_list)
-               trace_free_pid_list(no_pid_list);
+               trace_pid_list_free(no_pid_list);
 }
 
 void ftrace_clear_pids(struct trace_array *tr)
@@ -7428,7 +7646,7 @@ pid_write(struct file *filp, const char __user *ubuf,
 
        if (filtered_pids) {
                synchronize_rcu();
-               trace_free_pid_list(filtered_pids);
+               trace_pid_list_free(filtered_pids);
        } else if (pid_list && !other_pids) {
                /* Register a probe to set whether to ignore the tracing of a task */
                register_trace_sched_switch(ftrace_filter_pid_sched_switch_probe, tr);
@@ -7494,10 +7712,10 @@ static const struct file_operations ftrace_no_pid_fops = {
 
 void ftrace_init_tracefs(struct trace_array *tr, struct dentry *d_tracer)
 {
-       trace_create_file("set_ftrace_pid", 0644, d_tracer,
+       trace_create_file("set_ftrace_pid", TRACE_MODE_WRITE, d_tracer,
                            tr, &ftrace_pid_fops);
-       trace_create_file("set_ftrace_notrace_pid", 0644, d_tracer,
-                           tr, &ftrace_no_pid_fops);
+       trace_create_file("set_ftrace_notrace_pid", TRACE_MODE_WRITE,
+                         d_tracer, tr, &ftrace_no_pid_fops);
 }
 
 void __init ftrace_init_tracefs_toplevel(struct trace_array *tr,