Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,34 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for the powerpc trace subsystem
#
ifdef CONFIG_FUNCTION_TRACER
# do not trace tracer code
CFLAGS_REMOVE_ftrace.o = $(CC_FLAGS_FTRACE)
CFLAGS_REMOVE_ftrace_64_pg.o = $(CC_FLAGS_FTRACE)
endif
ifdef CONFIG_FUNCTION_TRACER
obj32-y += ftrace.o ftrace_entry.o
ifeq ($(CONFIG_MPROFILE_KERNEL)$(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY),)
obj64-y += ftrace_64_pg.o ftrace_64_pg_entry.o
else
obj64-y += ftrace.o ftrace_entry.o
endif
endif
obj-$(CONFIG_TRACING) += trace_clock.o
obj-$(CONFIG_PPC64) += $(obj64-y)
obj-$(CONFIG_PPC32) += $(obj32-y)
# Disable GCOV, KCOV & sanitizers in odd or sensitive code
GCOV_PROFILE_ftrace.o := n
KCOV_INSTRUMENT_ftrace.o := n
KCSAN_SANITIZE_ftrace.o := n
UBSAN_SANITIZE_ftrace.o := n
GCOV_PROFILE_ftrace_64_pg.o := n
KCOV_INSTRUMENT_ftrace_64_pg.o := n
KCSAN_SANITIZE_ftrace_64_pg.o := n
UBSAN_SANITIZE_ftrace_64_pg.o := n
@@ -0,0 +1,696 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Code for replacing ftrace calls with jumps.
*
* Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com>
*
* Thanks goes out to P.A. Semi, Inc for supplying me with a PPC64 box.
*
* Added function graph tracer code, taken from x86 that was written
* by Frederic Weisbecker, and ported to PPC by Steven Rostedt.
*
*/
#define pr_fmt(fmt) "ftrace-powerpc: " fmt
#include <linux/spinlock.h>
#include <linux/hardirq.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/ftrace.h>
#include <linux/percpu.h>
#include <linux/init.h>
#include <linux/list.h>
#include <asm/cacheflush.h>
#include <asm/text-patching.h>
#include <asm/ftrace.h>
#include <asm/syscall.h>
#include <asm/inst.h>
#include <asm/sections.h>
#define NUM_FTRACE_TRAMPS 2
static unsigned long ftrace_tramps[NUM_FTRACE_TRAMPS];
unsigned long ftrace_call_adjust(unsigned long addr)
{
if (addr >= (unsigned long)__exittext_begin && addr < (unsigned long)__exittext_end)
return 0;
if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY)) {
if (!IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE)) {
addr += MCOUNT_INSN_SIZE;
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS))
addr += MCOUNT_INSN_SIZE;
} else if (IS_ENABLED(CONFIG_CC_IS_CLANG) && IS_ENABLED(CONFIG_PPC64)) {
/*
* addr points to global entry point though the NOP was emitted at local
* entry point due to https://github.com/llvm/llvm-project/issues/163706
* Handle that here with ppc_function_entry() for kernel symbols while
* adjusting module addresses in the else case, by looking for the below
* module global entry point sequence:
* ld r2, -8(r12)
* add r2, r2, r12
*/
if (is_kernel_text(addr) || is_kernel_inittext(addr))
addr = ppc_function_entry((void *)addr);
else if ((ppc_inst_val(ppc_inst_read((u32 *)addr)) ==
PPC_RAW_LD(_R2, _R12, -8)) &&
(ppc_inst_val(ppc_inst_read((u32 *)(addr+4))) ==
PPC_RAW_ADD(_R2, _R2, _R12)))
addr += 8;
}
}
return addr;
}
static ppc_inst_t ftrace_create_branch_inst(unsigned long ip, unsigned long addr, int link)
{
ppc_inst_t op;
WARN_ON(!is_offset_in_branch_range(addr - ip));
create_branch(&op, (u32 *)ip, addr, link ? BRANCH_SET_LINK : 0);
return op;
}
static inline int ftrace_read_inst(unsigned long ip, ppc_inst_t *op)
{
if (copy_inst_from_kernel_nofault(op, (void *)ip)) {
pr_err("0x%lx: fetching instruction failed\n", ip);
return -EFAULT;
}
return 0;
}
static inline int ftrace_validate_inst(unsigned long ip, ppc_inst_t inst)
{
ppc_inst_t op;
int ret;
ret = ftrace_read_inst(ip, &op);
if (!ret && !ppc_inst_equal(op, inst)) {
pr_err("0x%lx: expected (%08lx) != found (%08lx)\n",
ip, ppc_inst_as_ulong(inst), ppc_inst_as_ulong(op));
ret = -EINVAL;
}
return ret;
}
static inline int ftrace_modify_code(unsigned long ip, ppc_inst_t old, ppc_inst_t new)
{
int ret = ftrace_validate_inst(ip, old);
if (!ret && !ppc_inst_equal(old, new))
ret = patch_instruction((u32 *)ip, new);
return ret;
}
static int is_bl_op(ppc_inst_t op)
{
return (ppc_inst_val(op) & ~PPC_LI_MASK) == PPC_RAW_BL(0);
}
static unsigned long find_ftrace_tramp(unsigned long ip)
{
int i;
for (i = 0; i < NUM_FTRACE_TRAMPS; i++)
if (!ftrace_tramps[i])
continue;
else if (is_offset_in_branch_range(ftrace_tramps[i] - ip))
return ftrace_tramps[i];
return 0;
}
#ifdef CONFIG_MODULES
static unsigned long ftrace_lookup_module_stub(unsigned long ip, unsigned long addr)
{
struct module *mod = NULL;
scoped_guard(rcu)
mod = __module_text_address(ip);
if (!mod)
pr_err("No module loaded at addr=%lx\n", ip);
return (addr == (unsigned long)ftrace_caller ? mod->arch.tramp : mod->arch.tramp_regs);
}
#else
static unsigned long ftrace_lookup_module_stub(unsigned long ip, unsigned long addr)
{
return 0;
}
#endif
static unsigned long ftrace_get_ool_stub(struct dyn_ftrace *rec)
{
#ifdef CONFIG_PPC_FTRACE_OUT_OF_LINE
return rec->arch.ool_stub;
#else
BUILD_BUG();
#endif
}
static int ftrace_get_call_inst(struct dyn_ftrace *rec, unsigned long addr, ppc_inst_t *call_inst)
{
unsigned long ip;
unsigned long stub;
if (IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE))
ip = ftrace_get_ool_stub(rec) + MCOUNT_INSN_SIZE; /* second instruction in stub */
else
ip = rec->ip;
if (!is_offset_in_branch_range(addr - ip) && addr != FTRACE_ADDR &&
addr != FTRACE_REGS_ADDR) {
/* This can only happen with ftrace direct */
if (!IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS)) {
pr_err("0x%lx (0x%lx): Unexpected target address 0x%lx\n",
ip, rec->ip, addr);
return -EINVAL;
}
addr = FTRACE_ADDR;
}
if (is_offset_in_branch_range(addr - ip))
/* Within range */
stub = addr;
else if (core_kernel_text(ip))
/* We would be branching to one of our ftrace stubs */
stub = find_ftrace_tramp(ip);
else
stub = ftrace_lookup_module_stub(ip, addr);
if (!stub) {
pr_err("0x%lx (0x%lx): No ftrace stubs reachable\n", ip, rec->ip);
return -EINVAL;
}
*call_inst = ftrace_create_branch_inst(ip, stub, 1);
return 0;
}
static int ftrace_init_ool_stub(struct module *mod, struct dyn_ftrace *rec)
{
#ifdef CONFIG_PPC_FTRACE_OUT_OF_LINE
static int ool_stub_text_index, ool_stub_text_end_index, ool_stub_inittext_index;
int ret = 0, ool_stub_count, *ool_stub_index;
ppc_inst_t inst;
/*
* See ftrace_entry.S if changing the below instruction sequence, as we rely on
* decoding the last branch instruction here to recover the correct function ip.
*/
struct ftrace_ool_stub *ool_stub, ool_stub_template = {
.insn = {
PPC_RAW_MFLR(_R0),
PPC_RAW_NOP(), /* bl ftrace_caller */
PPC_RAW_MTLR(_R0),
PPC_RAW_NOP() /* b rec->ip + 4 */
}
};
WARN_ON(rec->arch.ool_stub);
if (is_kernel_inittext(rec->ip)) {
ool_stub = ftrace_ool_stub_inittext;
ool_stub_index = &ool_stub_inittext_index;
ool_stub_count = ftrace_ool_stub_inittext_count;
} else if (is_kernel_text(rec->ip)) {
/*
* ftrace records are sorted, so we first use up the stub area within .text
* (ftrace_ool_stub_text) before using the area at the end of .text
* (ftrace_ool_stub_text_end), unless the stub is out of range of the record.
*/
if (ool_stub_text_index >= ftrace_ool_stub_text_count ||
!is_offset_in_branch_range((long)rec->ip -
(long)&ftrace_ool_stub_text[ool_stub_text_index])) {
ool_stub = ftrace_ool_stub_text_end;
ool_stub_index = &ool_stub_text_end_index;
ool_stub_count = ftrace_ool_stub_text_end_count;
} else {
ool_stub = ftrace_ool_stub_text;
ool_stub_index = &ool_stub_text_index;
ool_stub_count = ftrace_ool_stub_text_count;
}
#ifdef CONFIG_MODULES
} else if (mod) {
ool_stub = mod->arch.ool_stubs;
ool_stub_index = &mod->arch.ool_stub_index;
ool_stub_count = mod->arch.ool_stub_count;
#endif
} else {
return -EINVAL;
}
ool_stub += (*ool_stub_index)++;
if (WARN_ON(*ool_stub_index > ool_stub_count))
return -EINVAL;
if (!is_offset_in_branch_range((long)rec->ip - (long)&ool_stub->insn[0]) ||
!is_offset_in_branch_range((long)(rec->ip + MCOUNT_INSN_SIZE) -
(long)&ool_stub->insn[3])) {
pr_err("%s: ftrace ool stub out of range (%p -> %p).\n",
__func__, (void *)rec->ip, (void *)&ool_stub->insn[0]);
return -EINVAL;
}
rec->arch.ool_stub = (unsigned long)&ool_stub->insn[0];
/* bl ftrace_caller */
if (!mod)
ret = ftrace_get_call_inst(rec, (unsigned long)ftrace_caller, &inst);
#ifdef CONFIG_MODULES
else
/*
* We can't use ftrace_get_call_inst() since that uses
* __module_text_address(rec->ip) to look up the module.
* But, since the module is not fully formed at this stage,
* the lookup fails. We know the target though, so generate
* the branch inst directly.
*/
inst = ftrace_create_branch_inst(ftrace_get_ool_stub(rec) + MCOUNT_INSN_SIZE,
mod->arch.tramp, 1);
#endif
ool_stub_template.insn[1] = ppc_inst_val(inst);
/* b rec->ip + 4 */
if (!ret && create_branch(&inst, &ool_stub->insn[3], rec->ip + MCOUNT_INSN_SIZE, 0))
return -EINVAL;
ool_stub_template.insn[3] = ppc_inst_val(inst);
if (!ret)
ret = patch_instructions((u32 *)ool_stub, (u32 *)&ool_stub_template,
sizeof(ool_stub_template), false);
return ret;
#else /* !CONFIG_PPC_FTRACE_OUT_OF_LINE */
BUILD_BUG();
#endif
}
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
static const struct ftrace_ops *powerpc_rec_get_ops(struct dyn_ftrace *rec)
{
const struct ftrace_ops *ops = NULL;
if (rec->flags & FTRACE_FL_CALL_OPS_EN) {
ops = ftrace_find_unique_ops(rec);
WARN_ON_ONCE(!ops);
}
if (!ops)
ops = &ftrace_list_ops;
return ops;
}
static int ftrace_rec_set_ops(struct dyn_ftrace *rec, const struct ftrace_ops *ops)
{
if (IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE))
return patch_ulong((void *)(ftrace_get_ool_stub(rec) - sizeof(unsigned long)),
(unsigned long)ops);
else
return patch_ulong((void *)(rec->ip - MCOUNT_INSN_SIZE - sizeof(unsigned long)),
(unsigned long)ops);
}
static int ftrace_rec_set_nop_ops(struct dyn_ftrace *rec)
{
return ftrace_rec_set_ops(rec, &ftrace_nop_ops);
}
static int ftrace_rec_update_ops(struct dyn_ftrace *rec)
{
return ftrace_rec_set_ops(rec, powerpc_rec_get_ops(rec));
}
#else
static int ftrace_rec_set_nop_ops(struct dyn_ftrace *rec) { return 0; }
static int ftrace_rec_update_ops(struct dyn_ftrace *rec) { return 0; }
#endif
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, unsigned long addr)
{
/* This should never be called since we override ftrace_replace_code() */
WARN_ON(1);
return -EINVAL;
}
#endif
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
ppc_inst_t old, new;
unsigned long ip = rec->ip;
int ret = 0;
/* This can only ever be called during module load */
if (WARN_ON(!IS_ENABLED(CONFIG_MODULES) || core_kernel_text(ip)))
return -EINVAL;
old = ppc_inst(PPC_RAW_NOP());
if (IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE)) {
ip = ftrace_get_ool_stub(rec) + MCOUNT_INSN_SIZE; /* second instruction in stub */
ret = ftrace_get_call_inst(rec, (unsigned long)ftrace_caller, &old);
}
ret |= ftrace_get_call_inst(rec, addr, &new);
if (!ret)
ret = ftrace_modify_code(ip, old, new);
ret = ftrace_rec_update_ops(rec);
if (ret)
return ret;
if (!ret && IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE))
ret = ftrace_modify_code(rec->ip, ppc_inst(PPC_RAW_NOP()),
ppc_inst(PPC_RAW_BRANCH((long)ftrace_get_ool_stub(rec) - (long)rec->ip)));
return ret;
}
int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr)
{
/*
* This should never be called since we override ftrace_replace_code(),
* as well as ftrace_init_nop()
*/
WARN_ON(1);
return -EINVAL;
}
void ftrace_replace_code(int enable)
{
ppc_inst_t old, new, call_inst, new_call_inst;
ppc_inst_t nop_inst = ppc_inst(PPC_RAW_NOP());
unsigned long ip, new_addr, addr;
struct ftrace_rec_iter *iter;
struct dyn_ftrace *rec;
int ret = 0, update;
for_ftrace_rec_iter(iter) {
rec = ftrace_rec_iter_record(iter);
ip = rec->ip;
if (rec->flags & FTRACE_FL_DISABLED && !(rec->flags & FTRACE_FL_ENABLED))
continue;
addr = ftrace_get_addr_curr(rec);
new_addr = ftrace_get_addr_new(rec);
update = ftrace_update_record(rec, enable);
if (IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE) && update != FTRACE_UPDATE_IGNORE) {
ip = ftrace_get_ool_stub(rec) + MCOUNT_INSN_SIZE;
ret = ftrace_get_call_inst(rec, (unsigned long)ftrace_caller, &nop_inst);
if (ret)
goto out;
}
switch (update) {
case FTRACE_UPDATE_IGNORE:
default:
continue;
case FTRACE_UPDATE_MODIFY_CALL:
ret = ftrace_get_call_inst(rec, new_addr, &new_call_inst);
ret |= ftrace_get_call_inst(rec, addr, &call_inst);
ret |= ftrace_rec_update_ops(rec);
old = call_inst;
new = new_call_inst;
break;
case FTRACE_UPDATE_MAKE_NOP:
ret = ftrace_get_call_inst(rec, addr, &call_inst);
ret |= ftrace_rec_set_nop_ops(rec);
old = call_inst;
new = nop_inst;
break;
case FTRACE_UPDATE_MAKE_CALL:
ret = ftrace_get_call_inst(rec, new_addr, &call_inst);
ret |= ftrace_rec_update_ops(rec);
old = nop_inst;
new = call_inst;
break;
}
if (!ret)
ret = ftrace_modify_code(ip, old, new);
if (!ret && IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE) &&
(update == FTRACE_UPDATE_MAKE_NOP || update == FTRACE_UPDATE_MAKE_CALL)) {
/* Update the actual ftrace location */
call_inst = ppc_inst(PPC_RAW_BRANCH((long)ftrace_get_ool_stub(rec) -
(long)rec->ip));
nop_inst = ppc_inst(PPC_RAW_NOP());
ip = rec->ip;
if (update == FTRACE_UPDATE_MAKE_NOP)
ret = ftrace_modify_code(ip, call_inst, nop_inst);
else
ret = ftrace_modify_code(ip, nop_inst, call_inst);
if (ret)
goto out;
}
if (ret)
goto out;
}
out:
if (ret)
ftrace_bug(ret, rec);
return;
}
int ftrace_init_nop(struct module *mod, struct dyn_ftrace *rec)
{
unsigned long addr, ip = rec->ip;
ppc_inst_t old, new;
int ret = 0;
/* Verify instructions surrounding the ftrace location */
if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY)) {
/* Expect nops */
if (!IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE))
ret = ftrace_validate_inst(ip - 4, ppc_inst(PPC_RAW_NOP()));
if (!ret)
ret = ftrace_validate_inst(ip, ppc_inst(PPC_RAW_NOP()));
} else if (IS_ENABLED(CONFIG_PPC32)) {
/* Expected sequence: 'mflr r0', 'stw r0,4(r1)', 'bl _mcount' */
ret = ftrace_validate_inst(ip - 8, ppc_inst(PPC_RAW_MFLR(_R0)));
if (ret)
return ret;
ret = ftrace_modify_code(ip - 4, ppc_inst(PPC_RAW_STW(_R0, _R1, 4)),
ppc_inst(PPC_RAW_NOP()));
} else if (IS_ENABLED(CONFIG_MPROFILE_KERNEL)) {
/* Expected sequence: 'mflr r0', ['std r0,16(r1)'], 'bl _mcount' */
ret = ftrace_read_inst(ip - 4, &old);
if (!ret && !ppc_inst_equal(old, ppc_inst(PPC_RAW_MFLR(_R0)))) {
/* Gcc v5.x emit the additional 'std' instruction, gcc v6.x don't */
ret = ftrace_validate_inst(ip - 8, ppc_inst(PPC_RAW_MFLR(_R0)));
if (ret)
return ret;
ret = ftrace_modify_code(ip - 4, ppc_inst(PPC_RAW_STD(_R0, _R1, 16)),
ppc_inst(PPC_RAW_NOP()));
}
} else {
return -EINVAL;
}
if (ret)
return ret;
/* Set up out-of-line stub */
if (IS_ENABLED(CONFIG_PPC_FTRACE_OUT_OF_LINE)) {
ret = ftrace_init_ool_stub(mod, rec);
goto out;
}
/* Nop-out the ftrace location */
new = ppc_inst(PPC_RAW_NOP());
addr = MCOUNT_ADDR;
if (IS_ENABLED(CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY)) {
/* we instead patch-in the 'mflr r0' */
old = ppc_inst(PPC_RAW_NOP());
new = ppc_inst(PPC_RAW_MFLR(_R0));
ret = ftrace_modify_code(ip - 4, old, new);
} else if (is_offset_in_branch_range(addr - ip)) {
/* Within range */
old = ftrace_create_branch_inst(ip, addr, 1);
ret = ftrace_modify_code(ip, old, new);
} else if (core_kernel_text(ip) || (IS_ENABLED(CONFIG_MODULES) && mod)) {
/*
* We would be branching to a linker-generated stub, or to the module _mcount
* stub. Let's just confirm we have a 'bl' here.
*/
ret = ftrace_read_inst(ip, &old);
if (ret)
return ret;
if (!is_bl_op(old)) {
pr_err("0x%lx: expected (bl) != found (%08lx)\n", ip, ppc_inst_as_ulong(old));
return -EINVAL;
}
ret = patch_instruction((u32 *)ip, new);
} else {
return -EINVAL;
}
out:
if (!ret)
ret = ftrace_rec_set_nop_ops(rec);
return ret;
}
int ftrace_update_ftrace_func(ftrace_func_t func)
{
unsigned long ip = (unsigned long)(&ftrace_call);
ppc_inst_t old, new;
int ret;
/*
* When using CALL_OPS, the function to call is associated with the
* call site, and we don't have a global function pointer to update.
*/
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS))
return 0;
old = ppc_inst_read((u32 *)&ftrace_call);
new = ftrace_create_branch_inst(ip, ppc_function_entry(func), 1);
ret = ftrace_modify_code(ip, old, new);
/* Also update the regs callback function */
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS) && !ret) {
ip = (unsigned long)(&ftrace_regs_call);
old = ppc_inst_read((u32 *)&ftrace_regs_call);
new = ftrace_create_branch_inst(ip, ppc_function_entry(func), 1);
ret = ftrace_modify_code(ip, old, new);
}
return ret;
}
/*
* Use the default ftrace_modify_all_code, but without
* stop_machine().
*/
void arch_ftrace_update_code(int command)
{
ftrace_modify_all_code(command);
}
void ftrace_free_init_tramp(void)
{
int i;
for (i = 0; i < NUM_FTRACE_TRAMPS && ftrace_tramps[i]; i++)
if (ftrace_tramps[i] == (unsigned long)ftrace_tramp_init) {
ftrace_tramps[i] = 0;
return;
}
}
static void __init add_ftrace_tramp(unsigned long tramp)
{
int i;
for (i = 0; i < NUM_FTRACE_TRAMPS; i++)
if (!ftrace_tramps[i]) {
ftrace_tramps[i] = tramp;
return;
}
}
int __init ftrace_dyn_arch_init(void)
{
unsigned int *tramp[] = { ftrace_tramp_text, ftrace_tramp_init };
unsigned long addr = FTRACE_REGS_ADDR;
long reladdr;
int i;
u32 stub_insns[] = {
#ifdef CONFIG_PPC_KERNEL_PCREL
/* pla r12,addr */
PPC_PREFIX_MLS | __PPC_PRFX_R(1),
PPC_INST_PADDI | ___PPC_RT(_R12),
PPC_RAW_MTCTR(_R12),
PPC_RAW_BCTR()
#elif defined(CONFIG_PPC64)
PPC_RAW_LD(_R12, _R13, offsetof(struct paca_struct, kernel_toc)),
PPC_RAW_ADDIS(_R12, _R12, 0),
PPC_RAW_ADDI(_R12, _R12, 0),
PPC_RAW_MTCTR(_R12),
PPC_RAW_BCTR()
#else
PPC_RAW_LIS(_R12, 0),
PPC_RAW_ADDI(_R12, _R12, 0),
PPC_RAW_MTCTR(_R12),
PPC_RAW_BCTR()
#endif
};
if (IS_ENABLED(CONFIG_PPC_KERNEL_PCREL)) {
for (i = 0; i < 2; i++) {
reladdr = addr - (unsigned long)tramp[i];
if (reladdr >= (long)SZ_8G || reladdr < -(long)SZ_8G) {
pr_err("Address of %ps out of range of pcrel address.\n",
(void *)addr);
return -1;
}
memcpy(tramp[i], stub_insns, sizeof(stub_insns));
tramp[i][0] |= IMM_H18(reladdr);
tramp[i][1] |= IMM_L(reladdr);
add_ftrace_tramp((unsigned long)tramp[i]);
}
} else if (IS_ENABLED(CONFIG_PPC64)) {
reladdr = addr - kernel_toc_addr();
if (reladdr >= (long)SZ_2G || reladdr < -(long long)SZ_2G) {
pr_err("Address of %ps out of range of kernel_toc.\n",
(void *)addr);
return -1;
}
for (i = 0; i < 2; i++) {
memcpy(tramp[i], stub_insns, sizeof(stub_insns));
tramp[i][1] |= PPC_HA(reladdr);
tramp[i][2] |= PPC_LO(reladdr);
add_ftrace_tramp((unsigned long)tramp[i]);
}
} else {
for (i = 0; i < 2; i++) {
memcpy(tramp[i], stub_insns, sizeof(stub_insns));
tramp[i][0] |= PPC_HA(addr);
tramp[i][1] |= PPC_LO(addr);
add_ftrace_tramp((unsigned long)tramp[i]);
}
}
return 0;
}
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
void ftrace_graph_func(unsigned long ip, unsigned long parent_ip,
struct ftrace_ops *op, struct ftrace_regs *fregs)
{
unsigned long sp = arch_ftrace_regs(fregs)->regs.gpr[1];
if (unlikely(ftrace_graph_is_dead()))
goto out;
if (unlikely(atomic_read(&current->tracing_graph_pause)))
goto out;
if (!function_graph_enter_regs(parent_ip, ip, 0, (unsigned long *)sp, fregs))
parent_ip = ppc_function_entry(return_to_handler);
out:
arch_ftrace_regs(fregs)->regs.link = parent_ip;
}
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
@@ -0,0 +1,832 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Code for replacing ftrace calls with jumps.
*
* Copyright (C) 2007-2008 Steven Rostedt <srostedt@redhat.com>
*
* Thanks goes out to P.A. Semi, Inc for supplying me with a PPC64 box.
*
* Added function graph tracer code, taken from x86 that was written
* by Frederic Weisbecker, and ported to PPC by Steven Rostedt.
*
*/
#define pr_fmt(fmt) "ftrace-powerpc: " fmt
#include <linux/spinlock.h>
#include <linux/hardirq.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/ftrace.h>
#include <linux/percpu.h>
#include <linux/init.h>
#include <linux/list.h>
#include <asm/cacheflush.h>
#include <asm/text-patching.h>
#include <asm/ftrace.h>
#include <asm/syscall.h>
#include <asm/inst.h>
/*
* We generally only have a single long_branch tramp and at most 2 or 3 plt
* tramps generated. But, we don't use the plt tramps currently. We also allot
* 2 tramps after .text and .init.text. So, we only end up with around 3 usable
* tramps in total. Set aside 8 just to be sure.
*/
#define NUM_FTRACE_TRAMPS 8
static unsigned long ftrace_tramps[NUM_FTRACE_TRAMPS];
unsigned long ftrace_call_adjust(unsigned long addr)
{
return addr;
}
static ppc_inst_t
ftrace_call_replace(unsigned long ip, unsigned long addr, int link)
{
ppc_inst_t op;
addr = ppc_function_entry((void *)addr);
/* if (link) set op to 'bl' else 'b' */
create_branch(&op, (u32 *)ip, addr, link ? BRANCH_SET_LINK : 0);
return op;
}
static inline int
ftrace_modify_code(unsigned long ip, ppc_inst_t old, ppc_inst_t new)
{
ppc_inst_t replaced;
/*
* Note:
* We are paranoid about modifying text, as if a bug was to happen, it
* could cause us to read or write to someplace that could cause harm.
* Carefully read and modify the code with probe_kernel_*(), and make
* sure what we read is what we expected it to be before modifying it.
*/
/* read the text we want to modify */
if (copy_inst_from_kernel_nofault(&replaced, (void *)ip))
return -EFAULT;
/* Make sure it is what we expect it to be */
if (!ppc_inst_equal(replaced, old)) {
pr_err("%p: replaced (%08lx) != old (%08lx)", (void *)ip,
ppc_inst_as_ulong(replaced), ppc_inst_as_ulong(old));
return -EINVAL;
}
/* replace the text with the new text */
return patch_instruction((u32 *)ip, new);
}
/*
* Helper functions that are the same for both PPC64 and PPC32.
*/
static int test_24bit_addr(unsigned long ip, unsigned long addr)
{
addr = ppc_function_entry((void *)addr);
return is_offset_in_branch_range(addr - ip);
}
static int is_bl_op(ppc_inst_t op)
{
return (ppc_inst_val(op) & ~PPC_LI_MASK) == PPC_RAW_BL(0);
}
static int is_b_op(ppc_inst_t op)
{
return (ppc_inst_val(op) & ~PPC_LI_MASK) == PPC_RAW_BRANCH(0);
}
static unsigned long find_bl_target(unsigned long ip, ppc_inst_t op)
{
int offset;
offset = PPC_LI(ppc_inst_val(op));
/* make it signed */
if (offset & 0x02000000)
offset |= 0xfe000000;
return ip + (long)offset;
}
#ifdef CONFIG_MODULES
static struct module *ftrace_lookup_module(struct dyn_ftrace *rec)
{
struct module *mod;
scoped_guard(rcu)
mod = __module_text_address(rec->ip);
if (!mod)
pr_err("No module loaded at addr=%lx\n", rec->ip);
return mod;
}
static int
__ftrace_make_nop(struct module *mod,
struct dyn_ftrace *rec, unsigned long addr)
{
unsigned long entry, ptr, tramp;
unsigned long ip = rec->ip;
ppc_inst_t op, pop;
if (!mod) {
mod = ftrace_lookup_module(rec);
if (!mod)
return -EINVAL;
}
/* read where this goes */
if (copy_inst_from_kernel_nofault(&op, (void *)ip)) {
pr_err("Fetching opcode failed.\n");
return -EFAULT;
}
/* Make sure that this is still a 24bit jump */
if (!is_bl_op(op)) {
pr_err("Not expected bl: opcode is %08lx\n", ppc_inst_as_ulong(op));
return -EINVAL;
}
/* lets find where the pointer goes */
tramp = find_bl_target(ip, op);
pr_devel("ip:%lx jumps to %lx", ip, tramp);
if (module_trampoline_target(mod, tramp, &ptr)) {
pr_err("Failed to get trampoline target\n");
return -EFAULT;
}
pr_devel("trampoline target %lx", ptr);
entry = ppc_global_function_entry((void *)addr);
/* This should match what was called */
if (ptr != entry) {
pr_err("addr %lx does not match expected %lx\n", ptr, entry);
return -EINVAL;
}
if (IS_ENABLED(CONFIG_MPROFILE_KERNEL)) {
if (copy_inst_from_kernel_nofault(&op, (void *)(ip - 4))) {
pr_err("Fetching instruction at %lx failed.\n", ip - 4);
return -EFAULT;
}
/* We expect either a mflr r0, or a std r0, LRSAVE(r1) */
if (!ppc_inst_equal(op, ppc_inst(PPC_RAW_MFLR(_R0))) &&
!ppc_inst_equal(op, ppc_inst(PPC_INST_STD_LR))) {
pr_err("Unexpected instruction %08lx around bl _mcount\n",
ppc_inst_as_ulong(op));
return -EINVAL;
}
} else if (IS_ENABLED(CONFIG_PPC64)) {
/*
* Check what is in the next instruction. We can see ld r2,40(r1), but
* on first pass after boot we will see mflr r0.
*/
if (copy_inst_from_kernel_nofault(&op, (void *)(ip + 4))) {
pr_err("Fetching op failed.\n");
return -EFAULT;
}
if (!ppc_inst_equal(op, ppc_inst(PPC_INST_LD_TOC))) {
pr_err("Expected %08lx found %08lx\n", PPC_INST_LD_TOC,
ppc_inst_as_ulong(op));
return -EINVAL;
}
}
/*
* When using -mprofile-kernel or PPC32 there is no load to jump over.
*
* Otherwise our original call site looks like:
*
* bl <tramp>
* ld r2,XX(r1)
*
* Milton Miller pointed out that we can not simply nop the branch.
* If a task was preempted when calling a trace function, the nops
* will remove the way to restore the TOC in r2 and the r2 TOC will
* get corrupted.
*
* Use a b +8 to jump over the load.
*/
if (IS_ENABLED(CONFIG_MPROFILE_KERNEL) || IS_ENABLED(CONFIG_PPC32))
pop = ppc_inst(PPC_RAW_NOP());
else
pop = ppc_inst(PPC_RAW_BRANCH(8)); /* b +8 */
if (patch_instruction((u32 *)ip, pop)) {
pr_err("Patching NOP failed.\n");
return -EPERM;
}
return 0;
}
#else
static int __ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec, unsigned long addr)
{
return 0;
}
#endif /* CONFIG_MODULES */
static unsigned long find_ftrace_tramp(unsigned long ip)
{
int i;
/*
* We have the compiler generated long_branch tramps at the end
* and we prefer those
*/
for (i = NUM_FTRACE_TRAMPS - 1; i >= 0; i--)
if (!ftrace_tramps[i])
continue;
else if (is_offset_in_branch_range(ftrace_tramps[i] - ip))
return ftrace_tramps[i];
return 0;
}
static int add_ftrace_tramp(unsigned long tramp)
{
int i;
for (i = 0; i < NUM_FTRACE_TRAMPS; i++)
if (!ftrace_tramps[i]) {
ftrace_tramps[i] = tramp;
return 0;
}
return -1;
}
/*
* If this is a compiler generated long_branch trampoline (essentially, a
* trampoline that has a branch to _mcount()), we re-write the branch to
* instead go to ftrace_[regs_]caller() and note down the location of this
* trampoline.
*/
static int setup_mcount_compiler_tramp(unsigned long tramp)
{
int i;
ppc_inst_t op;
unsigned long ptr;
/* Is this a known long jump tramp? */
for (i = 0; i < NUM_FTRACE_TRAMPS; i++)
if (ftrace_tramps[i] == tramp)
return 0;
/* New trampoline -- read where this goes */
if (copy_inst_from_kernel_nofault(&op, (void *)tramp)) {
pr_debug("Fetching opcode failed.\n");
return -1;
}
/* Is this a 24 bit branch? */
if (!is_b_op(op)) {
pr_debug("Trampoline is not a long branch tramp.\n");
return -1;
}
/* lets find where the pointer goes */
ptr = find_bl_target(tramp, op);
if (ptr != ppc_global_function_entry((void *)_mcount)) {
pr_debug("Trampoline target %p is not _mcount\n", (void *)ptr);
return -1;
}
/* Let's re-write the tramp to go to ftrace_[regs_]caller */
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
ptr = ppc_global_function_entry((void *)ftrace_regs_caller);
else
ptr = ppc_global_function_entry((void *)ftrace_caller);
if (patch_branch((u32 *)tramp, ptr, 0)) {
pr_debug("REL24 out of range!\n");
return -1;
}
if (add_ftrace_tramp(tramp)) {
pr_debug("No tramp locations left\n");
return -1;
}
return 0;
}
static int __ftrace_make_nop_kernel(struct dyn_ftrace *rec, unsigned long addr)
{
unsigned long tramp, ip = rec->ip;
ppc_inst_t op;
/* Read where this goes */
if (copy_inst_from_kernel_nofault(&op, (void *)ip)) {
pr_err("Fetching opcode failed.\n");
return -EFAULT;
}
/* Make sure that this is still a 24bit jump */
if (!is_bl_op(op)) {
pr_err("Not expected bl: opcode is %08lx\n", ppc_inst_as_ulong(op));
return -EINVAL;
}
/* Let's find where the pointer goes */
tramp = find_bl_target(ip, op);
pr_devel("ip:%lx jumps to %lx", ip, tramp);
if (setup_mcount_compiler_tramp(tramp)) {
/* Are other trampolines reachable? */
if (!find_ftrace_tramp(ip)) {
pr_err("No ftrace trampolines reachable from %ps\n",
(void *)ip);
return -EINVAL;
}
}
if (patch_instruction((u32 *)ip, ppc_inst(PPC_RAW_NOP()))) {
pr_err("Patching NOP failed.\n");
return -EPERM;
}
return 0;
}
int ftrace_make_nop(struct module *mod,
struct dyn_ftrace *rec, unsigned long addr)
{
unsigned long ip = rec->ip;
ppc_inst_t old, new;
/*
* If the calling address is more that 24 bits away,
* then we had to use a trampoline to make the call.
* Otherwise just update the call site.
*/
if (test_24bit_addr(ip, addr)) {
/* within range */
old = ftrace_call_replace(ip, addr, 1);
new = ppc_inst(PPC_RAW_NOP());
return ftrace_modify_code(ip, old, new);
} else if (core_kernel_text(ip)) {
return __ftrace_make_nop_kernel(rec, addr);
} else if (!IS_ENABLED(CONFIG_MODULES)) {
return -EINVAL;
}
return __ftrace_make_nop(mod, rec, addr);
}
#ifdef CONFIG_MODULES
/*
* Examine the existing instructions for __ftrace_make_call.
* They should effectively be a NOP, and follow formal constraints,
* depending on the ABI. Return false if they don't.
*/
static bool expected_nop_sequence(void *ip, ppc_inst_t op0, ppc_inst_t op1)
{
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
return ppc_inst_equal(op0, ppc_inst(PPC_RAW_NOP()));
else
return ppc_inst_equal(op0, ppc_inst(PPC_RAW_BRANCH(8))) &&
ppc_inst_equal(op1, ppc_inst(PPC_INST_LD_TOC));
}
static int
__ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
ppc_inst_t op[2];
void *ip = (void *)rec->ip;
unsigned long entry, ptr, tramp;
struct module *mod = ftrace_lookup_module(rec);
if (!mod)
return -EINVAL;
/* read where this goes */
if (copy_inst_from_kernel_nofault(op, ip))
return -EFAULT;
if (!IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS) &&
copy_inst_from_kernel_nofault(op + 1, ip + 4))
return -EFAULT;
if (!expected_nop_sequence(ip, op[0], op[1])) {
pr_err("Unexpected call sequence at %p: %08lx %08lx\n", ip,
ppc_inst_as_ulong(op[0]), ppc_inst_as_ulong(op[1]));
return -EINVAL;
}
/* If we never set up ftrace trampoline(s), then bail */
if (!mod->arch.tramp ||
(IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS) && !mod->arch.tramp_regs)) {
pr_err("No ftrace trampoline\n");
return -EINVAL;
}
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS) && rec->flags & FTRACE_FL_REGS)
tramp = mod->arch.tramp_regs;
else
tramp = mod->arch.tramp;
if (module_trampoline_target(mod, tramp, &ptr)) {
pr_err("Failed to get trampoline target\n");
return -EFAULT;
}
pr_devel("trampoline target %lx", ptr);
entry = ppc_global_function_entry((void *)addr);
/* This should match what was called */
if (ptr != entry) {
pr_err("addr %lx does not match expected %lx\n", ptr, entry);
return -EINVAL;
}
if (patch_branch(ip, tramp, BRANCH_SET_LINK)) {
pr_err("REL24 out of range!\n");
return -EINVAL;
}
return 0;
}
#else
static int __ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
return 0;
}
#endif /* CONFIG_MODULES */
static int __ftrace_make_call_kernel(struct dyn_ftrace *rec, unsigned long addr)
{
ppc_inst_t op;
void *ip = (void *)rec->ip;
unsigned long tramp, entry, ptr;
/* Make sure we're being asked to patch branch to a known ftrace addr */
entry = ppc_global_function_entry((void *)ftrace_caller);
ptr = ppc_global_function_entry((void *)addr);
if (ptr != entry && IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
entry = ppc_global_function_entry((void *)ftrace_regs_caller);
if (ptr != entry) {
pr_err("Unknown ftrace addr to patch: %ps\n", (void *)ptr);
return -EINVAL;
}
/* Make sure we have a nop */
if (copy_inst_from_kernel_nofault(&op, ip)) {
pr_err("Unable to read ftrace location %p\n", ip);
return -EFAULT;
}
if (!ppc_inst_equal(op, ppc_inst(PPC_RAW_NOP()))) {
pr_err("Unexpected call sequence at %p: %08lx\n",
ip, ppc_inst_as_ulong(op));
return -EINVAL;
}
tramp = find_ftrace_tramp((unsigned long)ip);
if (!tramp) {
pr_err("No ftrace trampolines reachable from %ps\n", ip);
return -EINVAL;
}
if (patch_branch(ip, tramp, BRANCH_SET_LINK)) {
pr_err("Error patching branch to ftrace tramp!\n");
return -EINVAL;
}
return 0;
}
int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
unsigned long ip = rec->ip;
ppc_inst_t old, new;
/*
* If the calling address is more that 24 bits away,
* then we had to use a trampoline to make the call.
* Otherwise just update the call site.
*/
if (test_24bit_addr(ip, addr)) {
/* within range */
old = ppc_inst(PPC_RAW_NOP());
new = ftrace_call_replace(ip, addr, 1);
return ftrace_modify_code(ip, old, new);
} else if (core_kernel_text(ip)) {
return __ftrace_make_call_kernel(rec, addr);
} else if (!IS_ENABLED(CONFIG_MODULES)) {
/* We should not get here without modules */
return -EINVAL;
}
return __ftrace_make_call(rec, addr);
}
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
#ifdef CONFIG_MODULES
static int
__ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
unsigned long addr)
{
ppc_inst_t op;
unsigned long ip = rec->ip;
unsigned long entry, ptr, tramp;
struct module *mod = ftrace_lookup_module(rec);
if (!mod)
return -EINVAL;
/* If we never set up ftrace trampolines, then bail */
if (!mod->arch.tramp || !mod->arch.tramp_regs) {
pr_err("No ftrace trampoline\n");
return -EINVAL;
}
/* read where this goes */
if (copy_inst_from_kernel_nofault(&op, (void *)ip)) {
pr_err("Fetching opcode failed.\n");
return -EFAULT;
}
/* Make sure that this is still a 24bit jump */
if (!is_bl_op(op)) {
pr_err("Not expected bl: opcode is %08lx\n", ppc_inst_as_ulong(op));
return -EINVAL;
}
/* lets find where the pointer goes */
tramp = find_bl_target(ip, op);
entry = ppc_global_function_entry((void *)old_addr);
pr_devel("ip:%lx jumps to %lx", ip, tramp);
if (tramp != entry) {
/* old_addr is not within range, so we must have used a trampoline */
if (module_trampoline_target(mod, tramp, &ptr)) {
pr_err("Failed to get trampoline target\n");
return -EFAULT;
}
pr_devel("trampoline target %lx", ptr);
/* This should match what was called */
if (ptr != entry) {
pr_err("addr %lx does not match expected %lx\n", ptr, entry);
return -EINVAL;
}
}
/* The new target may be within range */
if (test_24bit_addr(ip, addr)) {
/* within range */
if (patch_branch((u32 *)ip, addr, BRANCH_SET_LINK)) {
pr_err("REL24 out of range!\n");
return -EINVAL;
}
return 0;
}
if (rec->flags & FTRACE_FL_REGS)
tramp = mod->arch.tramp_regs;
else
tramp = mod->arch.tramp;
if (module_trampoline_target(mod, tramp, &ptr)) {
pr_err("Failed to get trampoline target\n");
return -EFAULT;
}
pr_devel("trampoline target %lx", ptr);
entry = ppc_global_function_entry((void *)addr);
/* This should match what was called */
if (ptr != entry) {
pr_err("addr %lx does not match expected %lx\n", ptr, entry);
return -EINVAL;
}
if (patch_branch((u32 *)ip, tramp, BRANCH_SET_LINK)) {
pr_err("REL24 out of range!\n");
return -EINVAL;
}
return 0;
}
#else
static int __ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr, unsigned long addr)
{
return 0;
}
#endif
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
unsigned long addr)
{
unsigned long ip = rec->ip;
ppc_inst_t old, new;
/*
* If the calling address is more that 24 bits away,
* then we had to use a trampoline to make the call.
* Otherwise just update the call site.
*/
if (test_24bit_addr(ip, addr) && test_24bit_addr(ip, old_addr)) {
/* within range */
old = ftrace_call_replace(ip, old_addr, 1);
new = ftrace_call_replace(ip, addr, 1);
return ftrace_modify_code(ip, old, new);
} else if (core_kernel_text(ip)) {
/*
* We always patch out of range locations to go to the regs
* variant, so there is nothing to do here
*/
return 0;
} else if (!IS_ENABLED(CONFIG_MODULES)) {
/* We should not get here without modules */
return -EINVAL;
}
return __ftrace_modify_call(rec, old_addr, addr);
}
#endif
int ftrace_update_ftrace_func(ftrace_func_t func)
{
unsigned long ip = (unsigned long)(&ftrace_call);
ppc_inst_t old, new;
int ret;
old = ppc_inst_read((u32 *)&ftrace_call);
new = ftrace_call_replace(ip, (unsigned long)func, 1);
ret = ftrace_modify_code(ip, old, new);
/* Also update the regs callback function */
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS) && !ret) {
ip = (unsigned long)(&ftrace_regs_call);
old = ppc_inst_read((u32 *)&ftrace_regs_call);
new = ftrace_call_replace(ip, (unsigned long)func, 1);
ret = ftrace_modify_code(ip, old, new);
}
return ret;
}
/*
* Use the default ftrace_modify_all_code, but without
* stop_machine().
*/
void arch_ftrace_update_code(int command)
{
ftrace_modify_all_code(command);
}
#ifdef CONFIG_PPC64
#define PACATOC offsetof(struct paca_struct, kernel_toc)
extern unsigned int ftrace_tramp_text[], ftrace_tramp_init[];
void ftrace_free_init_tramp(void)
{
int i;
for (i = 0; i < NUM_FTRACE_TRAMPS && ftrace_tramps[i]; i++)
if (ftrace_tramps[i] == (unsigned long)ftrace_tramp_init) {
ftrace_tramps[i] = 0;
return;
}
}
int __init ftrace_dyn_arch_init(void)
{
int i;
unsigned int *tramp[] = { ftrace_tramp_text, ftrace_tramp_init };
u32 stub_insns[] = {
PPC_RAW_LD(_R12, _R13, PACATOC),
PPC_RAW_ADDIS(_R12, _R12, 0),
PPC_RAW_ADDI(_R12, _R12, 0),
PPC_RAW_MTCTR(_R12),
PPC_RAW_BCTR()
};
unsigned long addr;
long reladdr;
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_REGS))
addr = ppc_global_function_entry((void *)ftrace_regs_caller);
else
addr = ppc_global_function_entry((void *)ftrace_caller);
reladdr = addr - kernel_toc_addr();
if (reladdr >= SZ_2G || reladdr < -(long)SZ_2G) {
pr_err("Address of %ps out of range of kernel_toc.\n",
(void *)addr);
return -1;
}
for (i = 0; i < 2; i++) {
memcpy(tramp[i], stub_insns, sizeof(stub_insns));
tramp[i][1] |= PPC_HA(reladdr);
tramp[i][2] |= PPC_LO(reladdr);
add_ftrace_tramp((unsigned long)tramp[i]);
}
return 0;
}
#endif
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
extern void ftrace_graph_call(void);
extern void ftrace_graph_stub(void);
static int ftrace_modify_ftrace_graph_caller(bool enable)
{
unsigned long ip = (unsigned long)(&ftrace_graph_call);
unsigned long addr = (unsigned long)(&ftrace_graph_caller);
unsigned long stub = (unsigned long)(&ftrace_graph_stub);
ppc_inst_t old, new;
if (IS_ENABLED(CONFIG_DYNAMIC_FTRACE_WITH_ARGS))
return 0;
old = ftrace_call_replace(ip, enable ? stub : addr, 0);
new = ftrace_call_replace(ip, enable ? addr : stub, 0);
return ftrace_modify_code(ip, old, new);
}
int ftrace_enable_ftrace_graph_caller(void)
{
return ftrace_modify_ftrace_graph_caller(true);
}
int ftrace_disable_ftrace_graph_caller(void)
{
return ftrace_modify_ftrace_graph_caller(false);
}
/*
* Hook the return address and push it in the stack of return addrs
* in current thread info. Return the address we want to divert to.
*/
static unsigned long
__prepare_ftrace_return(unsigned long parent, unsigned long ip, unsigned long sp,
struct ftrace_regs *fregs)
{
unsigned long return_hooker;
if (unlikely(ftrace_graph_is_dead()))
goto out;
if (unlikely(atomic_read(&current->tracing_graph_pause)))
goto out;
return_hooker = ppc_function_entry(return_to_handler);
if (!function_graph_enter_regs(parent, ip, 0, (unsigned long *)sp, fregs))
parent = return_hooker;
out:
return parent;
}
#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)
{
arch_ftrace_regs(fregs)->regs.link = __prepare_ftrace_return(parent_ip, ip,
arch_ftrace_regs(fregs)->regs.gpr[1], fregs);
}
#else
unsigned long prepare_ftrace_return(unsigned long parent, unsigned long ip,
unsigned long sp)
{
return __prepare_ftrace_return(parent, ip, sp, NULL);
}
#endif
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
#ifdef CONFIG_PPC64_ELF_ABI_V1
char *arch_ftrace_match_adjust(char *str, const char *search)
{
if (str[0] == '.' && search[0] != '.')
return str + 1;
else
return str;
}
#endif /* CONFIG_PPC64_ELF_ABI_V1 */
@@ -0,0 +1,132 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Split from ftrace_64.S
*/
#include <linux/export.h>
#include <linux/magic.h>
#include <asm/ppc_asm.h>
#include <asm/asm-offsets.h>
#include <asm/ftrace.h>
#include <asm/ppc-opcode.h>
_GLOBAL_TOC(ftrace_caller)
lbz r3, PACA_FTRACE_ENABLED(r13)
cmpdi r3, 0
beqlr
/* Taken from output of objdump from lib64/glibc */
mflr r3
ld r11, 0(r1)
stdu r1, -112(r1)
std r3, 128(r1)
ld r4, 16(r11)
subi r3, r3, MCOUNT_INSN_SIZE
.globl ftrace_call
ftrace_call:
bl ftrace_stub
nop
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
.globl ftrace_graph_call
ftrace_graph_call:
b ftrace_graph_stub
_GLOBAL(ftrace_graph_stub)
#endif
ld r0, 128(r1)
mtlr r0
addi r1, r1, 112
_GLOBAL(ftrace_stub)
blr
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
_GLOBAL(ftrace_graph_caller)
addi r5, r1, 112
/* load r4 with local address */
ld r4, 128(r1)
subi r4, r4, MCOUNT_INSN_SIZE
/* Grab the LR out of the caller stack frame */
ld r11, 112(r1)
ld r3, 16(r11)
bl prepare_ftrace_return
nop
/*
* prepare_ftrace_return gives us the address we divert to.
* Change the LR in the callers stack frame to this.
*/
ld r11, 112(r1)
std r3, 16(r11)
ld r0, 128(r1)
mtlr r0
addi r1, r1, 112
blr
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
.pushsection ".tramp.ftrace.text","aw",@progbits;
.globl ftrace_tramp_text
ftrace_tramp_text:
.space 32
.popsection
.pushsection ".tramp.ftrace.init","aw",@progbits;
.globl ftrace_tramp_init
ftrace_tramp_init:
.space 32
.popsection
_GLOBAL(mcount)
_GLOBAL(_mcount)
EXPORT_SYMBOL(_mcount)
mflr r12
mtctr r12
mtlr r0
bctr
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
_GLOBAL(return_to_handler)
/* need to save return values */
#ifdef CONFIG_PPC64
std r4, -32(r1)
std r3, -24(r1)
/* save TOC */
std r2, -16(r1)
std r31, -8(r1)
mr r31, r1
stdu r1, -112(r1)
/*
* We might be called from a module.
* Switch to our TOC to run inside the core kernel.
*/
LOAD_PACA_TOC()
#else
stwu r1, -16(r1)
stw r3, 8(r1)
stw r4, 12(r1)
#endif
bl ftrace_return_to_handler
nop
/* return value has real return address */
mtlr r3
#ifdef CONFIG_PPC64
ld r1, 0(r1)
ld r4, -32(r1)
ld r3, -24(r1)
ld r2, -16(r1)
ld r31, -8(r1)
#else
lwz r3, 8(r1)
lwz r4, 12(r1)
addi r1, r1, 16
#endif
/* Jump back to real return address */
blr
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
@@ -0,0 +1,479 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Split from ftrace_64.S
*/
#include <linux/export.h>
#include <linux/magic.h>
#include <asm/ppc_asm.h>
#include <asm/asm-offsets.h>
#include <asm/ftrace.h>
#include <asm/ppc-opcode.h>
#include <asm/thread_info.h>
#include <asm/bug.h>
#include <asm/ptrace.h>
/*
*
* ftrace_caller()/ftrace_regs_caller() is the function that replaces _mcount()
* when ftrace is active.
*
* We arrive here after a function A calls function B, and we are the trace
* function for B. When we enter r1 points to A's stack frame, B has not yet
* had a chance to allocate one yet.
*
* Additionally r2 may point either to the TOC for A, or B, depending on
* whether B did a TOC setup sequence before calling us.
*
* On entry the LR points back to the _mcount() call site, and r0 holds the
* saved LR as it was on entry to B, ie. the original return address at the
* call site in A.
*
* Our job is to save the register state into a struct pt_regs (on the stack)
* and then arrange for the ftrace function to be called.
*/
.macro ftrace_regs_entry allregs
/* Create a minimal stack frame for representing B */
PPC_STLU r1, -STACK_FRAME_MIN_SIZE(r1)
/* Create our stack frame + pt_regs */
PPC_STLU r1,-SWITCH_FRAME_SIZE(r1)
.if \allregs == 1
SAVE_GPRS(11, 12, r1)
.endif
/* Get the _mcount() call site out of LR */
mflr r11
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
/* Load the ftrace_op */
PPC_LL r12, -(MCOUNT_INSN_SIZE*2 + SZL)(r11)
/* Load direct_call from the ftrace_op */
PPC_LL r12, FTRACE_OPS_DIRECT_CALL(r12)
PPC_LCMPI r12, 0
.if \allregs == 1
bne .Lftrace_direct_call_regs
.else
bne .Lftrace_direct_call
.endif
#endif
/* Save the previous LR in pt_regs->link */
PPC_STL r0, _LINK(r1)
/* Also save it in A's stack frame */
PPC_STL r0, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE+LRSAVE(r1)
/* Save all gprs to pt_regs */
SAVE_GPR(0, r1)
SAVE_GPRS(3, 10, r1)
#ifdef CONFIG_PPC64
/* Ok to continue? */
lbz r3, PACA_FTRACE_ENABLED(r13)
cmpdi r3, 0
beq ftrace_no_trace
#endif
.if \allregs == 1
SAVE_GPR(2, r1)
SAVE_GPRS(13, 31, r1)
.else
#if defined(CONFIG_LIVEPATCH_64) || defined(CONFIG_PPC_FTRACE_OUT_OF_LINE)
SAVE_GPR(14, r1)
#endif
.endif
/* Save previous stack pointer (r1) */
addi r8, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
PPC_STL r8, GPR1(r1)
.if \allregs == 1
/* Load special regs for save below */
mfcr r7
mfmsr r8
mfctr r9
mfxer r10
.else
/* Clear MSR to flag as ftrace_caller versus frace_regs_caller */
li r8, 0
.endif
#ifdef CONFIG_PPC64
/* Save callee's TOC in the ABI compliant location */
std r2, STK_GOT(r1)
LOAD_PACA_TOC() /* get kernel TOC in r2 */
#endif
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
/* r11 points to the instruction following the call to ftrace */
PPC_LL r5, -(MCOUNT_INSN_SIZE*2 + SZL)(r11)
PPC_LL r12, FTRACE_OPS_FUNC(r5)
mtctr r12
#else /* !CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS */
#ifdef CONFIG_PPC64
LOAD_REG_ADDR(r3, function_trace_op)
ld r5,0(r3)
#else
lis r3,function_trace_op@ha
lwz r5,function_trace_op@l(r3)
#endif
#endif
/* Save special regs */
PPC_STL r8, _MSR(r1)
.if \allregs == 1
PPC_STL r7, _CCR(r1)
PPC_STL r9, _CTR(r1)
PPC_STL r10, _XER(r1)
.endif
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
/* Clear orig_gpr3 to later detect ftrace_direct call */
li r7, 0
PPC_STL r7, ORIG_GPR3(r1)
#endif
#ifdef CONFIG_PPC_FTRACE_OUT_OF_LINE
/* Save our real return address in nvr for return */
.if \allregs == 0
SAVE_GPR(15, r1)
.endif
mr r15, r11
/*
* We want the ftrace location in the function, but our lr (in r11)
* points at the 'mtlr r0' instruction in the out of line stub. To
* recover the ftrace location, we read the branch instruction in the
* stub, and adjust our lr by the branch offset.
*
* See ftrace_init_ool_stub() for the profile sequence.
*/
lwz r8, MCOUNT_INSN_SIZE(r11)
slwi r8, r8, 6
srawi r8, r8, 6
add r3, r11, r8
/*
* Override our nip to point past the branch in the original function.
* This allows reliable stack trace and the ftrace stack tracer to work as-is.
*/
addi r11, r3, MCOUNT_INSN_SIZE
#else
/* Calculate ip from nip-4 into r3 for call below */
subi r3, r11, MCOUNT_INSN_SIZE
#endif
/* Save NIP as pt_regs->nip */
PPC_STL r11, _NIP(r1)
/* Also save it in B's stackframe header for proper unwind */
PPC_STL r11, LRSAVE+SWITCH_FRAME_SIZE(r1)
#if defined(CONFIG_LIVEPATCH_64) || defined(CONFIG_PPC_FTRACE_OUT_OF_LINE)
mr r14, r11 /* remember old NIP */
#endif
/* Put the original return address in r4 as parent_ip */
mr r4, r0
/* Load &pt_regs in r6 for call below */
addi r6, r1, STACK_INT_FRAME_REGS
.endm
.macro ftrace_regs_exit allregs
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
/* Check orig_gpr3 to detect ftrace_direct call */
PPC_LL r3, ORIG_GPR3(r1)
PPC_LCMPI cr1, r3, 0
mtctr r3
#endif
/* Restore possibly modified LR */
PPC_LL r0, _LINK(r1)
#ifndef CONFIG_PPC_FTRACE_OUT_OF_LINE
/* Load ctr with the possibly modified NIP */
PPC_LL r3, _NIP(r1)
#ifdef CONFIG_LIVEPATCH_64
cmpd r14, r3 /* has NIP been altered? */
#endif
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
beq cr1,2f
mtlr r3
b 3f
#endif
2: mtctr r3
mtlr r0
3:
#else /* !CONFIG_PPC_FTRACE_OUT_OF_LINE */
/* Load LR with the possibly modified NIP */
PPC_LL r3, _NIP(r1)
cmpd r14, r3 /* has NIP been altered? */
bne- 1f
mr r3, r15
1: mtlr r3
.if \allregs == 0
REST_GPR(15, r1)
.endif
#endif
/* Restore gprs */
.if \allregs == 1
REST_GPRS(2, 31, r1)
.else
REST_GPRS(3, 10, r1)
#if defined(CONFIG_LIVEPATCH_64) || defined(CONFIG_PPC_FTRACE_OUT_OF_LINE)
REST_GPR(14, r1)
#endif
.endif
#ifdef CONFIG_PPC64
/* Restore callee's TOC */
ld r2, STK_GOT(r1)
#endif
/* Pop our stack frame */
addi r1, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
#ifdef CONFIG_LIVEPATCH_64
/* Based on the cmpd above, if the NIP was altered handle livepatch */
bne- livepatch_handler
#endif
/* jump after _mcount site */
#ifdef CONFIG_PPC_FTRACE_OUT_OF_LINE
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
bnectr cr1
#endif
/*
* Return with blr to keep the link stack balanced. The function profiling sequence
* uses 'mtlr r0' to restore LR.
*/
blr
#else
bctr
#endif
.endm
.macro ftrace_regs_func allregs
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_CALL_OPS
bctrl
#else
.if \allregs == 1
.globl ftrace_regs_call
ftrace_regs_call:
.else
.globl ftrace_call
ftrace_call:
.endif
/* ftrace_call(r3, r4, r5, r6) */
bl ftrace_stub
#endif
.endm
_GLOBAL(ftrace_regs_caller)
ftrace_regs_entry 1
ftrace_regs_func 1
ftrace_regs_exit 1
_GLOBAL(ftrace_caller)
ftrace_regs_entry 0
ftrace_regs_func 0
ftrace_regs_exit 0
_GLOBAL(ftrace_stub)
blr
#ifdef CONFIG_PPC64
ftrace_no_trace:
#ifdef CONFIG_PPC_FTRACE_OUT_OF_LINE
REST_GPR(3, r1)
addi r1, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
blr
#else
mflr r3
mtctr r3
REST_GPR(3, r1)
addi r1, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
mtlr r0
bctr
#endif
#endif
#ifdef CONFIG_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
.Lftrace_direct_call_regs:
mtctr r12
REST_GPRS(11, 12, r1)
addi r1, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
bctr
.Lftrace_direct_call:
mtctr r12
addi r1, r1, SWITCH_FRAME_SIZE+STACK_FRAME_MIN_SIZE
bctr
SYM_FUNC_START(ftrace_stub_direct_tramp)
blr
SYM_FUNC_END(ftrace_stub_direct_tramp)
#endif
#ifdef CONFIG_LIVEPATCH_64
/*
* This function runs in the mcount context, between two functions. As
* such it can only clobber registers which are volatile and used in
* function linkage.
*
* We get here when a function A, calls another function B, but B has
* been live patched with a new function C.
*
* On entry, we have no stack frame and can not allocate one.
*
* With PPC_FTRACE_OUT_OF_LINE=n, on entry:
* - LR points back to the original caller (in A)
* - CTR holds the new NIP in C
* - r0, r11 & r12 are free
*
* With PPC_FTRACE_OUT_OF_LINE=y, on entry:
* - r0 points back to the original caller (in A)
* - LR holds the new NIP in C
* - r11 & r12 are free
*/
livepatch_handler:
ld r12, PACA_THREAD_INFO(r13)
/* Allocate 3 x 8 bytes */
ld r11, TI_livepatch_sp(r12)
addi r11, r11, 24
std r11, TI_livepatch_sp(r12)
/* Store stack end marker */
lis r12, STACK_END_MAGIC@h
ori r12, r12, STACK_END_MAGIC@l
std r12, -8(r11)
/* Save toc & real LR on livepatch stack */
std r2, -24(r11)
#ifndef CONFIG_PPC_FTRACE_OUT_OF_LINE
mflr r12
std r12, -16(r11)
mfctr r12
#else
std r0, -16(r11)
mflr r12
/* Put ctr in r12 for global entry and branch there */
mtctr r12
#endif
bctrl
/*
* Now we are returning from the patched function to the original
* caller A. We are free to use r11, r12 and we can use r2 until we
* restore it.
*/
ld r12, PACA_THREAD_INFO(r13)
ld r11, TI_livepatch_sp(r12)
/* Check stack marker hasn't been trashed */
lis r2, STACK_END_MAGIC@h
ori r2, r2, STACK_END_MAGIC@l
ld r12, -8(r11)
1: tdne r12, r2
EMIT_BUG_ENTRY 1b, __FILE__, __LINE__ - 1, 0
/* Restore LR & toc from livepatch stack */
ld r12, -16(r11)
mtlr r12
ld r2, -24(r11)
/* Pop livepatch stack frame */
ld r12, PACA_THREAD_INFO(r13)
subi r11, r11, 24
std r11, TI_livepatch_sp(r12)
/* Return to original caller of live patched function */
blr
#endif /* CONFIG_LIVEPATCH */
#ifndef CONFIG_ARCH_USING_PATCHABLE_FUNCTION_ENTRY
_GLOBAL(mcount)
_GLOBAL(_mcount)
EXPORT_SYMBOL(_mcount)
mflr r12
mtctr r12
mtlr r0
bctr
#endif
#ifdef CONFIG_FUNCTION_GRAPH_TRACER
_GLOBAL(return_to_handler)
/* need to save return values */
#ifdef CONFIG_PPC64
stdu r1, -SWITCH_FRAME_SIZE(r1)
std r4, GPR4(r1)
std r3, GPR3(r1)
/* Save previous stack pointer (r1) */
addi r3, r1, SWITCH_FRAME_SIZE
std r3, GPR1(r1)
/* save TOC */
std r2, 24(r1)
std r31, 32(r1)
mr r31, r1
/* pass ftrace_regs/pt_regs to ftrace_return_to_handler */
addi r3, r1, STACK_INT_FRAME_REGS
/*
* We might be called from a module.
* Switch to our TOC to run inside the core kernel.
*/
LOAD_PACA_TOC()
#else
stwu r1, -SWITCH_FRAME_SIZE(r1)
stw r4, GPR4(r1)
stw r3, GPR3(r1)
addi r3, r1, SWITCH_FRAME_SIZE
stw r3, GPR1(r1)
/* pass ftrace_regs/pt_regs to ftrace_return_to_handler */
addi r3, r1, STACK_INT_FRAME_REGS
#endif
bl ftrace_return_to_handler
nop
/* return value has real return address */
mtlr r3
#ifdef CONFIG_PPC64
ld r4, GPR4(r1)
ld r3, GPR3(r1)
ld r2, 24(r1)
ld r31, 32(r1)
ld r1, 0(r1)
#else
lwz r3, GPR3(r1)
lwz r4, GPR4(r1)
addi r1, r1, SWITCH_FRAME_SIZE
#endif
/* Jump back to real return address */
blr
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
#ifdef CONFIG_PPC_FTRACE_OUT_OF_LINE
SYM_DATA(ftrace_ool_stub_text_count, .long CONFIG_PPC_FTRACE_OUT_OF_LINE_NUM_RESERVE)
SYM_START(ftrace_ool_stub_text, SYM_L_GLOBAL, .balign SZL)
.space CONFIG_PPC_FTRACE_OUT_OF_LINE_NUM_RESERVE * FTRACE_OOL_STUB_SIZE
SYM_CODE_END(ftrace_ool_stub_text)
#endif
.pushsection ".tramp.ftrace.text","aw",@progbits;
.globl ftrace_tramp_text
ftrace_tramp_text:
.space 32
.popsection
.pushsection ".tramp.ftrace.init","aw",@progbits;
.globl ftrace_tramp_init
ftrace_tramp_init:
.space 32
.popsection
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
*
* Copyright (C) 2015 Naveen N. Rao, IBM Corporation
*/
#include <asm/trace_clock.h>
#include <asm/time.h>
u64 notrace trace_clock_ppc_tb(void)
{
return get_tb();
}