mirror of
https://git.proxmox.com/git/mirror_ubuntu-kernels.git
synced 2025-11-17 04:36:09 +00:00
This commit replaces riscv's support for FTRACE_WITH_REGS with support
for FTRACE_WITH_ARGS. This is required for the ongoing effort to stop
relying on stop_machine() for RISCV's implementation of ftrace.
The main relevant benefit that this change will bring for the above
use-case is that now we don't have separate ftrace_caller and
ftrace_regs_caller trampolines. This will allow the callsite to call
ftrace_caller by modifying a single instruction. Now the callsite can
do something similar to:
When not tracing: | When tracing:
func: func:
auipc t0, ftrace_caller_top auipc t0, ftrace_caller_top
nop <=========<Enable/Disable>=========> jalr t0, ftrace_caller_bottom
[...] [...]
The above assumes that we are dropping the support of calling a direct
trampoline from the callsite. We need to drop this as the callsite can't
change the target address to call, it can only enable/disable a call to
a preset target (ftrace_caller in the above diagram). We can later optimize
this by calling an intermediate dispatcher trampoline before ftrace_caller.
Currently, ftrace_regs_caller saves all CPU registers in the format of
struct pt_regs and allows the tracer to modify them. We don't need to
save all of the CPU registers because at function entry only a subset of
pt_regs is live:
|----------+----------+---------------------------------------------|
| Register | ABI Name | Description |
|----------+----------+---------------------------------------------|
| x1 | ra | Return address for traced function |
| x2 | sp | Stack pointer |
| x5 | t0 | Return address for ftrace_caller trampoline |
| x8 | s0/fp | Frame pointer |
| x10-11 | a0-1 | Function arguments/return values |
| x12-17 | a2-7 | Function arguments |
|----------+----------+---------------------------------------------|
See RISCV calling convention[1] for the above table.
Saving just the live registers decreases the amount of stack space
required from 288 Bytes to 112 Bytes.
Basic testing was done with this on the VisionFive 2 development board.
Note:
- Moving from REGS to ARGS will mean that RISCV will stop supporting
KPROBES_ON_FTRACE as it requires full pt_regs to be saved.
- KPROBES_ON_FTRACE will be supplanted by FPROBES see [2].
[1] https://riscv.org/wp-content/uploads/2015/01/riscv-calling.pdf
[2] https://lore.kernel.org/all/170887410337.564249.6360118840946697039.stgit@devnote2/
Signed-off-by: Puranjay Mohan <puranjay@kernel.org>
Tested-by: Björn Töpel <bjorn@rivosinc.com>
Reviewed-by: Björn Töpel <bjorn@rivosinc.com>
Link: https://lore.kernel.org/r/20240405142453.4187-1-puranjay@kernel.org
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
235 lines
5.8 KiB
C
235 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (C) 2013 Linaro Limited
|
|
* Author: AKASHI Takahiro <takahiro.akashi@linaro.org>
|
|
* Copyright (C) 2017 Andes Technology Corporation
|
|
*/
|
|
|
|
#include <linux/ftrace.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/memory.h>
|
|
#include <linux/stop_machine.h>
|
|
#include <asm/cacheflush.h>
|
|
#include <asm/patch.h>
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
void ftrace_arch_code_modify_prepare(void) __acquires(&text_mutex)
|
|
{
|
|
mutex_lock(&text_mutex);
|
|
|
|
/*
|
|
* The code sequences we use for ftrace can't be patched while the
|
|
* kernel is running, so we need to use stop_machine() to modify them
|
|
* for now. This doesn't play nice with text_mutex, we use this flag
|
|
* to elide the check.
|
|
*/
|
|
riscv_patch_in_stop_machine = true;
|
|
}
|
|
|
|
void ftrace_arch_code_modify_post_process(void) __releases(&text_mutex)
|
|
{
|
|
riscv_patch_in_stop_machine = false;
|
|
mutex_unlock(&text_mutex);
|
|
}
|
|
|
|
static int ftrace_check_current_call(unsigned long hook_pos,
|
|
unsigned int *expected)
|
|
{
|
|
unsigned int replaced[2];
|
|
unsigned int nops[2] = {NOP4, NOP4};
|
|
|
|
/* we expect nops at the hook position */
|
|
if (!expected)
|
|
expected = nops;
|
|
|
|
/*
|
|
* Read the text we want to modify;
|
|
* return must be -EFAULT on read error
|
|
*/
|
|
if (copy_from_kernel_nofault(replaced, (void *)hook_pos,
|
|
MCOUNT_INSN_SIZE))
|
|
return -EFAULT;
|
|
|
|
/*
|
|
* Make sure it is what we expect it to be;
|
|
* return must be -EINVAL on failed comparison
|
|
*/
|
|
if (memcmp(expected, replaced, sizeof(replaced))) {
|
|
pr_err("%p: expected (%08x %08x) but got (%08x %08x)\n",
|
|
(void *)hook_pos, expected[0], expected[1], replaced[0],
|
|
replaced[1]);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __ftrace_modify_call(unsigned long hook_pos, unsigned long target,
|
|
bool enable, bool ra)
|
|
{
|
|
unsigned int call[2];
|
|
unsigned int nops[2] = {NOP4, NOP4};
|
|
|
|
if (ra)
|
|
make_call_ra(hook_pos, target, call);
|
|
else
|
|
make_call_t0(hook_pos, target, call);
|
|
|
|
/* Replace the auipc-jalr pair at once. Return -EPERM on write error. */
|
|
if (patch_insn_write((void *)hook_pos, enable ? call : nops, MCOUNT_INSN_SIZE))
|
|
return -EPERM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
|
|
{
|
|
unsigned int call[2];
|
|
|
|
make_call_t0(rec->ip, addr, call);
|
|
|
|
if (patch_insn_write((void *)rec->ip, call, MCOUNT_INSN_SIZE))
|
|
return -EPERM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
|
|
unsigned long addr)
|
|
{
|
|
unsigned int nops[2] = {NOP4, NOP4};
|
|
|
|
if (patch_insn_write((void *)rec->ip, nops, MCOUNT_INSN_SIZE))
|
|
return -EPERM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This is called early on, and isn't wrapped by
|
|
* ftrace_arch_code_modify_{prepare,post_process}() and therefor doesn't hold
|
|
* text_mutex, which triggers a lockdep failure. SMP isn't running so we could
|
|
* just directly poke the text, but it's simpler to just take the lock
|
|
* ourselves.
|
|
*/
|
|
int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
|
|
{
|
|
int out;
|
|
|
|
mutex_lock(&text_mutex);
|
|
out = ftrace_make_nop(mod, rec, MCOUNT_ADDR);
|
|
mutex_unlock(&text_mutex);
|
|
|
|
return out;
|
|
}
|
|
|
|
int ftrace_update_ftrace_func(ftrace_func_t func)
|
|
{
|
|
int ret = __ftrace_modify_call((unsigned long)&ftrace_call,
|
|
(unsigned long)func, true, true);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct ftrace_modify_param {
|
|
int command;
|
|
atomic_t cpu_count;
|
|
};
|
|
|
|
static int __ftrace_modify_code(void *data)
|
|
{
|
|
struct ftrace_modify_param *param = data;
|
|
|
|
if (atomic_inc_return(¶m->cpu_count) == num_online_cpus()) {
|
|
ftrace_modify_all_code(param->command);
|
|
/*
|
|
* Make sure the patching store is effective *before* we
|
|
* increment the counter which releases all waiting CPUs
|
|
* by using the release variant of atomic increment. The
|
|
* release pairs with the call to local_flush_icache_all()
|
|
* on the waiting CPU.
|
|
*/
|
|
atomic_inc_return_release(¶m->cpu_count);
|
|
} else {
|
|
while (atomic_read(¶m->cpu_count) <= num_online_cpus())
|
|
cpu_relax();
|
|
}
|
|
|
|
local_flush_icache_all();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void arch_ftrace_update_code(int command)
|
|
{
|
|
struct ftrace_modify_param param = { command, ATOMIC_INIT(0) };
|
|
|
|
stop_machine(__ftrace_modify_code, ¶m, cpu_online_mask);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
|
|
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
|
|
unsigned long addr)
|
|
{
|
|
unsigned int call[2];
|
|
unsigned long caller = rec->ip;
|
|
int ret;
|
|
|
|
make_call_t0(caller, old_addr, call);
|
|
ret = ftrace_check_current_call(caller, call);
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
return __ftrace_modify_call(caller, addr, true, false);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
|
|
/*
|
|
* Most of this function is copied from arm64.
|
|
*/
|
|
void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
|
|
unsigned long frame_pointer)
|
|
{
|
|
unsigned long return_hooker = (unsigned long)&return_to_handler;
|
|
unsigned long old;
|
|
|
|
if (unlikely(atomic_read(¤t->tracing_graph_pause)))
|
|
return;
|
|
|
|
/*
|
|
* We don't suffer access faults, so no extra fault-recovery assembly
|
|
* is needed here.
|
|
*/
|
|
old = *parent;
|
|
|
|
if (!function_graph_enter(old, self_addr, frame_pointer, parent))
|
|
*parent = return_hooker;
|
|
}
|
|
|
|
#ifdef CONFIG_DYNAMIC_FTRACE
|
|
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_ARGS
|
|
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
|
|
struct ftrace_ops *op, struct ftrace_regs *fregs)
|
|
{
|
|
prepare_ftrace_return(&fregs->ra, ip, fregs->s0);
|
|
}
|
|
#else /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
|
|
extern void ftrace_graph_call(void);
|
|
int ftrace_enable_ftrace_graph_caller(void)
|
|
{
|
|
return __ftrace_modify_call((unsigned long)&ftrace_graph_call,
|
|
(unsigned long)&prepare_ftrace_return, true, true);
|
|
}
|
|
|
|
int ftrace_disable_ftrace_graph_caller(void)
|
|
{
|
|
return __ftrace_modify_call((unsigned long)&ftrace_graph_call,
|
|
(unsigned long)&prepare_ftrace_return, false, true);
|
|
}
|
|
#endif /* CONFIG_DYNAMIC_FTRACE_WITH_ARGS */
|
|
#endif /* CONFIG_DYNAMIC_FTRACE */
|
|
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
|