Merge branch 'fixes' into features
[linux-2.6-microblaze.git] / arch / s390 / kernel / ftrace.c
index 21d62d8..d8b96c5 100644 (file)
@@ -4,8 +4,7 @@
  *
  * Copyright IBM Corp. 2009,2014
  *
- *   Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>,
- *             Martin Schwidefsky <schwidefsky@de.ibm.com>
+ *   Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
  */
 
 #include <linux/moduleloader.h>
@@ -159,37 +158,73 @@ int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
        return 0;
 }
 
+static struct ftrace_hotpatch_trampoline *ftrace_get_trampoline(struct dyn_ftrace *rec)
+{
+       struct ftrace_hotpatch_trampoline *trampoline;
+       struct ftrace_insn insn;
+       s64 disp;
+       u16 opc;
+
+       if (copy_from_kernel_nofault(&insn, (void *)rec->ip, sizeof(insn)))
+               return ERR_PTR(-EFAULT);
+       disp = (s64)insn.disp * 2;
+       trampoline = (void *)(rec->ip + disp);
+       if (get_kernel_nofault(opc, &trampoline->brasl_opc))
+               return ERR_PTR(-EFAULT);
+       if (opc != 0xc015)
+               return ERR_PTR(-EINVAL);
+       return trampoline;
+}
+
 int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
                       unsigned long addr)
 {
+       struct ftrace_hotpatch_trampoline *trampoline;
+       u64 old;
+
+       trampoline = ftrace_get_trampoline(rec);
+       if (IS_ERR(trampoline))
+               return PTR_ERR(trampoline);
+       if (get_kernel_nofault(old, &trampoline->interceptor))
+               return -EFAULT;
+       if (old != old_addr)
+               return -EINVAL;
+       s390_kernel_write(&trampoline->interceptor, &addr, sizeof(addr));
        return 0;
 }
 
-static void brcl_disable(void *brcl)
+static int ftrace_patch_branch_mask(void *addr, u16 expected, bool enable)
 {
-       u8 op = 0x04; /* set mask field to zero */
+       u16 old;
+       u8 op;
 
-       s390_kernel_write((char *)brcl + 1, &op, sizeof(op));
+       if (get_kernel_nofault(old, addr))
+               return -EFAULT;
+       if (old != expected)
+               return -EINVAL;
+       /* set mask field to all ones or zeroes */
+       op = enable ? 0xf4 : 0x04;
+       s390_kernel_write((char *)addr + 1, &op, sizeof(op));
+       return 0;
 }
 
 int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
                    unsigned long addr)
 {
-       brcl_disable((void *)rec->ip);
-       return 0;
-}
-
-static void brcl_enable(void *brcl)
-{
-       u8 op = 0xf4; /* set mask field to all ones */
-
-       s390_kernel_write((char *)brcl + 1, &op, sizeof(op));
+       /* Expect brcl 0xf,... */
+       return ftrace_patch_branch_mask((void *)rec->ip, 0xc0f4, false);
 }
 
 int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
 {
-       brcl_enable((void *)rec->ip);
-       return 0;
+       struct ftrace_hotpatch_trampoline *trampoline;
+
+       trampoline = ftrace_get_trampoline(rec);
+       if (IS_ERR(trampoline))
+               return PTR_ERR(trampoline);
+       s390_kernel_write(&trampoline->interceptor, &addr, sizeof(addr));
+       /* Expect brcl 0x0,... */
+       return ftrace_patch_branch_mask((void *)rec->ip, 0xc004, true);
 }
 
 int ftrace_update_ftrace_func(ftrace_func_t func)
@@ -262,14 +297,24 @@ NOKPROBE_SYMBOL(prepare_ftrace_return);
  */
 int ftrace_enable_ftrace_graph_caller(void)
 {
-       brcl_disable(ftrace_graph_caller);
+       int rc;
+
+       /* Expect brc 0xf,... */
+       rc = ftrace_patch_branch_mask(ftrace_graph_caller, 0xa7f4, false);
+       if (rc)
+               return rc;
        text_poke_sync_lock();
        return 0;
 }
 
 int ftrace_disable_ftrace_graph_caller(void)
 {
-       brcl_enable(ftrace_graph_caller);
+       int rc;
+
+       /* Expect brc 0x0,... */
+       rc = ftrace_patch_branch_mask(ftrace_graph_caller, 0xa704, true);
+       if (rc)
+               return rc;
        text_poke_sync_lock();
        return 0;
 }
@@ -291,7 +336,7 @@ void kprobe_ftrace_handler(unsigned long ip, unsigned long parent_ip,
 
        regs = ftrace_get_regs(fregs);
        p = get_kprobe((kprobe_opcode_t *)ip);
-       if (unlikely(!p) || kprobe_disabled(p))
+       if (!regs || unlikely(!p) || kprobe_disabled(p))
                goto out;
 
        if (kprobe_running()) {