summaryrefslogtreecommitdiff
path: root/arch
diff options
context:
space:
mode:
authorYouling Tang <tangyouling@loongson.cn>2023-05-01 17:19:53 +0800
committerHuacai Chen <chenhuacai@loongson.cn>2023-05-01 17:19:53 +0800
commit24d4f52791dae16e478741cc397ba660a95b9d02 (patch)
treed1b552c205a947981d405e57b517d111e5a6d885 /arch
parent819cf65575230bca02ad669daadba2ac04187ae1 (diff)
LoongArch: ftrace: Implement ftrace_find_callable_addr() to simplify code
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>
Diffstat (limited to 'arch')
-rw-r--r--arch/loongarch/kernel/ftrace_dyn.c116
1 files changed, 57 insertions, 59 deletions
diff --git a/arch/loongarch/kernel/ftrace_dyn.c b/arch/loongarch/kernel/ftrace_dyn.c
index c5f4b4681ddc..eb624af93617 100644
--- a/arch/loongarch/kernel/ftrace_dyn.c
+++ b/arch/loongarch/kernel/ftrace_dyn.c
@@ -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);