#include <linux/ftrace.h>
 #include <linux/percpu.h>
 #include <linux/sched.h>
+#include <linux/slab.h>
 #include <linux/init.h>
 #include <linux/list.h>
 #include <linux/module.h>
 {
        return 0;
 }
-#endif
-
-#ifdef CONFIG_FUNCTION_GRAPH_TRACER
-
-#ifdef CONFIG_DYNAMIC_FTRACE
-extern void ftrace_graph_call(void);
 
+#if defined(CONFIG_X86_64) || defined(CONFIG_FUNCTION_GRAPH_TRACER)
 static unsigned char *ftrace_jmp_replace(unsigned long ip, unsigned long addr)
 {
        static union ftrace_code_union calc;
         */
        return calc.code;
 }
+#endif
+
+/* Currently only x86_64 supports dynamic trampolines */
+#ifdef CONFIG_X86_64
+
+#ifdef CONFIG_MODULES
+#include <linux/moduleloader.h>
+/* Module allocation simplifies allocating memory for code */
+static inline void *alloc_tramp(unsigned long size)
+{
+       return module_alloc(size);
+}
+static inline void tramp_free(void *tramp)
+{
+       module_free(NULL, tramp);
+}
+#else
+/* Trampolines can only be created if modules are supported */
+static inline void *alloc_tramp(unsigned long size)
+{
+       return NULL;
+}
+static inline void tramp_free(void *tramp) { }
+#endif
+
+/* Defined as markers to the end of the ftrace default trampolines */
+extern void ftrace_caller_end(void);
+extern void ftrace_regs_caller_end(void);
+extern void ftrace_return(void);
+extern void ftrace_caller_op_ptr(void);
+extern void ftrace_regs_caller_op_ptr(void);
+
+/* movq function_trace_op(%rip), %rdx */
+/* 0x48 0x8b 0x15 <offset-to-ftrace_trace_op (4 bytes)> */
+#define OP_REF_SIZE    7
+
+/*
+ * The ftrace_ops is passed to the function callback. Since the
+ * trampoline only services a single ftrace_ops, we can pass in
+ * that ops directly.
+ *
+ * The ftrace_op_code_union is used to create a pointer to the
+ * ftrace_ops that will be passed to the callback function.
+ */
+union ftrace_op_code_union {
+       char code[OP_REF_SIZE];
+       struct {
+               char op[3];
+               int offset;
+       } __attribute__((packed));
+};
+
+static unsigned long create_trampoline(struct ftrace_ops *ops)
+{
+       unsigned const char *jmp;
+       unsigned long start_offset;
+       unsigned long end_offset;
+       unsigned long op_offset;
+       unsigned long offset;
+       unsigned long size;
+       unsigned long ip;
+       unsigned long *ptr;
+       void *trampoline;
+       /* 48 8b 15 <offset> is movq <offset>(%rip), %rdx */
+       unsigned const char op_ref[] = { 0x48, 0x8b, 0x15 };
+       union ftrace_op_code_union op_ptr;
+       int ret;
+
+       if (ops->flags & FTRACE_OPS_FL_SAVE_REGS) {
+               start_offset = (unsigned long)ftrace_regs_caller;
+               end_offset = (unsigned long)ftrace_regs_caller_end;
+               op_offset = (unsigned long)ftrace_regs_caller_op_ptr;
+       } else {
+               start_offset = (unsigned long)ftrace_caller;
+               end_offset = (unsigned long)ftrace_caller_end;
+               op_offset = (unsigned long)ftrace_caller_op_ptr;
+       }
+
+       size = end_offset - start_offset;
+
+       /*
+        * Allocate enough size to store the ftrace_caller code,
+        * the jmp to ftrace_return, as well as the address of
+        * the ftrace_ops this trampoline is used for.
+        */
+       trampoline = alloc_tramp(size + MCOUNT_INSN_SIZE + sizeof(void *));
+       if (!trampoline)
+               return 0;
+
+       /* Copy ftrace_caller onto the trampoline memory */
+       ret = probe_kernel_read(trampoline, (void *)start_offset, size);
+       if (WARN_ON(ret < 0)) {
+               tramp_free(trampoline);
+               return 0;
+       }
+
+       ip = (unsigned long)trampoline + size;
+
+       /* The trampoline ends with a jmp to ftrace_return */
+       jmp = ftrace_jmp_replace(ip, (unsigned long)ftrace_return);
+       memcpy(trampoline + size, jmp, MCOUNT_INSN_SIZE);
+
+       /*
+        * The address of the ftrace_ops that is used for this trampoline
+        * is stored at the end of the trampoline. This will be used to
+        * load the third parameter for the callback. Basically, that
+        * location at the end of the trampoline takes the place of
+        * the global function_trace_op variable.
+        */
+
+       ptr = (unsigned long *)(trampoline + size + MCOUNT_INSN_SIZE);
+       *ptr = (unsigned long)ops;
+
+       op_offset -= start_offset;
+       memcpy(&op_ptr, trampoline + op_offset, OP_REF_SIZE);
+
+       /* Are we pointing to the reference? */
+       if (WARN_ON(memcmp(op_ptr.op, op_ref, 3) != 0)) {
+               tramp_free(trampoline);
+               return 0;
+       }
+
+       /* Load the contents of ptr into the callback parameter */
+       offset = (unsigned long)ptr;
+       offset -= (unsigned long)trampoline + op_offset + OP_REF_SIZE;
+
+       op_ptr.offset = offset;
+
+       /* put in the new offset to the ftrace_ops */
+       memcpy(trampoline + op_offset, &op_ptr, OP_REF_SIZE);
+
+       /* ALLOC_TRAMP flags lets us know we created it */
+       ops->flags |= FTRACE_OPS_FL_ALLOC_TRAMP;
+
+       return (unsigned long)trampoline;
+}
+
+void arch_ftrace_update_trampoline(struct ftrace_ops *ops)
+{
+       ftrace_func_t func;
+       unsigned char *new;
+       unsigned long start_offset;
+       unsigned long call_offset;
+       unsigned long offset;
+       unsigned long ip;
+       int ret;
+
+       if (ops->trampoline) {
+               /*
+                * The ftrace_ops caller may set up its own trampoline.
+                * In such a case, this code must not modify it.
+                */
+               if (!(ops->flags & FTRACE_OPS_FL_ALLOC_TRAMP))
+                       return;
+       } else {
+               ops->trampoline = create_trampoline(ops);
+               if (!ops->trampoline)
+                       return;
+       }
+
+       if (ops->flags & FTRACE_OPS_FL_SAVE_REGS) {
+               start_offset = (unsigned long)ftrace_regs_caller;
+               call_offset = (unsigned long)ftrace_regs_call;
+       } else {
+               start_offset = (unsigned long)ftrace_caller;
+               call_offset = (unsigned long)ftrace_call;
+       }
+
+       offset = call_offset - start_offset;
+       ip = ops->trampoline + offset;
+
+       func = ftrace_ops_get_func(ops);
+
+       /* Do a safe modify in case the trampoline is executing */
+       new = ftrace_call_replace(ip, (unsigned long)func);
+       ret = update_ftrace_func(ip, new);
+
+       /* The update should never fail */
+       WARN_ON(ret);
+}
+#endif /* CONFIG_X86_64 */
+#endif /* CONFIG_DYNAMIC_FTRACE */
+
+#ifdef CONFIG_FUNCTION_GRAPH_TRACER
+
+#ifdef CONFIG_DYNAMIC_FTRACE
+extern void ftrace_graph_call(void);
 
 static int ftrace_mod_jmp(unsigned long ip, void *func)
 {
 
        return ret;
 }
 
+static void ftrace_update_trampoline(struct ftrace_ops *ops);
+
 static int __register_ftrace_function(struct ftrace_ops *ops)
 {
        if (ops->flags & FTRACE_OPS_FL_DELETED)
        } else
                add_ftrace_ops(&ftrace_ops_list, ops);
 
+       ftrace_update_trampoline(ops);
+
        if (ftrace_enabled)
                update_ftrace_function();
 
 {
        struct ftrace_iterator *iter;
 
-       if (unlikely(ftrace_disabled))
-               return -ENODEV;
-
        iter = __seq_open_private(file, &show_ftrace_seq_ops, sizeof(*iter));
        if (iter) {
                iter->pg = ftrace_pages_start;
 static char ftrace_graph_notrace_buf[FTRACE_FILTER_SIZE] __initdata;
 static int ftrace_set_func(unsigned long *array, int *idx, int size, char *buffer);
 
+static unsigned long save_global_trampoline;
+static unsigned long save_global_flags;
+
 static int __init set_graph_function(char *str)
 {
        strlcpy(ftrace_graph_buf, str, FTRACE_FILTER_SIZE);
        ftrace_disabled = 1;
 }
 
+/* Do nothing if arch does not support this */
+void __weak arch_ftrace_update_trampoline(struct ftrace_ops *ops)
+{
+}
+
+static void ftrace_update_trampoline(struct ftrace_ops *ops)
+{
+       /* Currently, only non dynamic ops can have a trampoline */
+       if (ops->flags & FTRACE_OPS_FL_DYNAMIC)
+               return;
+
+       arch_ftrace_update_trampoline(ops);
+}
+
 #else
 
 static struct ftrace_ops global_ops = {
        return 1;
 }
 
+static void ftrace_update_trampoline(struct ftrace_ops *ops)
+{
+}
+
 #endif /* CONFIG_DYNAMIC_FTRACE */
 
 __init void ftrace_init_global_array_ops(struct trace_array *tr)
        update_function_graph_func();
 
        ret = ftrace_startup(&graph_ops, FTRACE_START_FUNC_RET);
-
 out:
        mutex_unlock(&ftrace_lock);
        return ret;
        unregister_pm_notifier(&ftrace_suspend_notifier);
        unregister_trace_sched_switch(ftrace_graph_probe_sched_switch, NULL);
 
+#ifdef CONFIG_DYNAMIC_FTRACE
+       /*
+        * Function graph does not allocate the trampoline, but
+        * other global_ops do. We need to reset the ALLOC_TRAMP flag
+        * if one was used.
+        */
+       global_ops.trampoline = save_global_trampoline;
+       if (save_global_flags & FTRACE_OPS_FL_ALLOC_TRAMP)
+               global_ops.flags |= FTRACE_OPS_FL_ALLOC_TRAMP;
+#endif
+
  out:
        mutex_unlock(&ftrace_lock);
 }