LoongArch: ftrace: Implement ftrace_find_callable_addr() to simplify code
authorYouling Tang <tangyouling@loongson.cn>
Mon, 1 May 2023 09:19:53 +0000 (17:19 +0800)
committerHuacai Chen <chenhuacai@loongson.cn>
Mon, 1 May 2023 09:19:53 +0000 (17:19 +0800)
In the module processing functions, the same logic can be reused by
implementing ftrace_find_callable_addr().

Signed-off-by: Youling Tang <tangyouling@loongson.cn>
Signed-off-by: Huacai Chen <chenhuacai@loongson.cn>
arch/loongarch/kernel/ftrace_dyn.c

index c5f4b46..eb624af 100644 (file)
@@ -31,16 +31,11 @@ static int ftrace_modify_code(unsigned long pc, u32 old, u32 new, bool validate)
 }
 
 #ifdef CONFIG_MODULES
-static inline int __get_mod(struct module **mod, unsigned long addr)
+static bool reachable_by_bl(unsigned long addr, unsigned long pc)
 {
-       preempt_disable();
-       *mod = __module_text_address(addr);
-       preempt_enable();
+       long offset = (long)addr - (long)pc;
 
-       if (WARN_ON(!(*mod)))
-               return -EINVAL;
-
-       return 0;
+       return offset >= -SZ_128M && offset < SZ_128M;
 }
 
 static struct plt_entry *get_ftrace_plt(struct module *mod, unsigned long addr)
@@ -56,17 +51,58 @@ static struct plt_entry *get_ftrace_plt(struct module *mod, unsigned long addr)
        return NULL;
 }
 
-static unsigned long get_plt_addr(struct module *mod, unsigned long addr)
+/*
+ * Find the address the callsite must branch to in order to reach '*addr'.
+ *
+ * Due to the limited range of 'bl' instruction, modules may be placed too far
+ * away to branch directly and we must use a PLT.
+ *
+ * Returns true when '*addr' contains a reachable target address, or has been
+ * modified to contain a PLT address. Returns false otherwise.
+ */
+static bool ftrace_find_callable_addr(struct dyn_ftrace *rec, struct module *mod, unsigned long *addr)
 {
+       unsigned long pc = rec->ip + LOONGARCH_INSN_SIZE;
        struct plt_entry *plt;
 
-       plt = get_ftrace_plt(mod, addr);
+       /*
+        * When the target is within range of the 'bl' instruction, use 'addr'
+        * as-is and branch to that directly.
+        */
+       if (reachable_by_bl(*addr, pc))
+               return true;
+
+       /*
+        * 'mod' is only set at module load time, but if we end up
+        * dealing with an out-of-range condition, we can assume it
+        * is due to a module being loaded far away from the kernel.
+        *
+        * NOTE: __module_text_address() must be called with preemption
+        * disabled, but we can rely on ftrace_lock to ensure that 'mod'
+        * retains its validity throughout the remainder of this code.
+        */
+       if (!mod) {
+               preempt_disable();
+               mod = __module_text_address(pc);
+               preempt_enable();
+       }
+
+       if (WARN_ON(!mod))
+               return false;
+
+       plt = get_ftrace_plt(mod, *addr);
        if (!plt) {
-               pr_err("ftrace: no module PLT for %ps\n", (void *)addr);
-               return -EINVAL;
+               pr_err("ftrace: no module PLT for %ps\n", (void *)*addr);
+               return false;
        }
 
-       return (unsigned long)plt;
+       *addr = (unsigned long)plt;
+       return true;
+}
+#else /* !CONFIG_MODULES */
+static bool ftrace_find_callable_addr(struct dyn_ftrace *rec, struct module *mod, unsigned long *addr)
+{
+       return true;
 }
 #endif
 
@@ -75,26 +111,14 @@ int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, unsigned
 {
        u32 old, new;
        unsigned long pc;
-       long offset __maybe_unused;
 
        pc = rec->ip + LOONGARCH_INSN_SIZE;
 
-#ifdef CONFIG_MODULES
-       offset = (long)pc - (long)addr;
-
-       if (offset < -SZ_128M || offset >= SZ_128M) {
-               int ret;
-               struct module *mod;
-
-               ret = __get_mod(&mod, pc);
-               if (ret)
-                       return ret;
-
-               addr = get_plt_addr(mod, addr);
+       if (!ftrace_find_callable_addr(rec, NULL, &addr))
+               return -EINVAL;
 
-               old_addr = get_plt_addr(mod, old_addr);
-       }
-#endif
+       if (!ftrace_find_callable_addr(rec, NULL, &old_addr))
+               return -EINVAL;
 
        new = larch_insn_gen_bl(pc, addr);
        old = larch_insn_gen_bl(pc, old_addr);
@@ -151,24 +175,11 @@ int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
 {
        u32 old, new;
        unsigned long pc;
-       long offset __maybe_unused;
 
        pc = rec->ip + LOONGARCH_INSN_SIZE;
 
-#ifdef CONFIG_MODULES
-       offset = (long)pc - (long)addr;
-
-       if (offset < -SZ_128M || offset >= SZ_128M) {
-               int ret;
-               struct module *mod;
-
-               ret = __get_mod(&mod, pc);
-               if (ret)
-                       return ret;
-
-               addr = get_plt_addr(mod, addr);
-       }
-#endif
+       if (!ftrace_find_callable_addr(rec, NULL, &addr))
+               return -EINVAL;
 
        old = larch_insn_gen_nop();
        new = larch_insn_gen_bl(pc, addr);
@@ -180,24 +191,11 @@ int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long ad
 {
        u32 old, new;
        unsigned long pc;
-       long offset __maybe_unused;
 
        pc = rec->ip + LOONGARCH_INSN_SIZE;
 
-#ifdef CONFIG_MODULES
-       offset = (long)pc - (long)addr;
-
-       if (offset < -SZ_128M || offset >= SZ_128M) {
-               int ret;
-               struct module *mod;
-
-               ret = __get_mod(&mod, pc);
-               if (ret)
-                       return ret;
-
-               addr = get_plt_addr(mod, addr);
-       }
-#endif
+       if (!ftrace_find_callable_addr(rec, NULL, &addr))
+               return -EINVAL;
 
        new = larch_insn_gen_nop();
        old = larch_insn_gen_bl(pc, addr);