// 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/asm-prototypes.h>
#include <asm/cacheflush.h>
#include <asm/code-patching.h>
#include <asm/ftrace.h>
#include <asm/syscall.h>
#include <asm/inst.h>
#ifdef CONFIG_DYNAMIC_FTRACE
/*
* 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];
static struct ppc_inst
ftrace_call_replace(unsigned long ip, unsigned long addr, int link)
{
struct ppc_inst op;
addr = ppc_function_entry((void *)addr);
/* if (link) set op to 'bl' else 'b' */
create_branch(&op, (struct ppc_inst *)ip, addr, link ? 1 : 0);
return op;
}
static int
ftrace_modify_code(unsigned long ip, struct ppc_inst old, struct ppc_inst new)
{
struct ppc_inst 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 (probe_kernel_read_inst(&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 (%#x) != old (%#x)",
(void *)ip, ppc_inst_val(replaced), ppc_inst_val(old));
return -EINVAL;
}
/* replace the text with the new text */
if (patch_instruction((struct ppc_inst *)ip, new))
return -EPERM;
return 0;
}
/*
* Helper functions that are the same for both PPC64 and PPC32.
*/
static int test_24bit_addr(unsigned long ip, unsigned long addr)
{
struct ppc_inst op;
addr = ppc_function_entry((void *)addr);
/* use the create_branch to verify that this offset can be branched */
return create_branch(&op, (struct ppc_inst *)ip, addr, 0) == 0;
}
static int is_bl_op(struct ppc_inst op)
{
return (ppc_inst_val(op) & 0xfc000003) == 0x48000001;
}
static int is_b_op(struct ppc_inst op)
{
return (ppc_inst_val(op) & 0xfc000003) == 0x48000000;
}
static unsigned long find_bl_target(unsigned long ip, struct ppc_inst op)
{
int offset;
offset = (ppc_inst_val(op) & 0x03fffffc);
/* make it signed */
if (offset & 0x02000000)
offset |= 0xfe000000;
return ip + (long)offset;
}
#ifdef CONFIG_MODULES
#ifdef CONFIG_PPC64
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;
struct ppc_inst op, pop;
/* read where this goes */
if (probe_kernel_read_inst(&op, (void *)ip)) {
pr_err("Fetching opcode failed.\n");
return -EFAULT;
}
/* Make sure that that this is still a 24bit jump */
if (!is_bl_op(op)) {
pr_err("Not expected bl: opcode is %x\n", ppc_inst_val(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;
}
#ifdef CONFIG_MPROFILE_KERNEL
/* When using -mkernel_profile there is no load to jump over */
pop = ppc_inst(PPC_INST_NOP);
if (probe_kernel_read_inst(&op, (void *)(ip - 4))) {
pr_err(