Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 1cead2d7 authored by Palmer Dabbelt's avatar Palmer Dabbelt
Browse files

RISC-V: Add dynamic ftrace support for RISC-V platforms

This patch set includes the building blocks of dynamic ftrace features
for RISC-V machines.  I'm afraid I'm not very familiar with ftrace, but
the code looks OK to me.  It's been used to track down a performance
problem in our SPI driver and appears to work acceptably, but we haven't
given it a whole lot of banging yet so there might still be some bugs
lurking around somewhere.
parents 0adb3285 b785ec12
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -114,6 +114,9 @@ config ARCH_RV64I
	select 64BIT
	select HAVE_FUNCTION_TRACER
	select HAVE_FUNCTION_GRAPH_TRACER
	select HAVE_FTRACE_MCOUNT_RECORD
	select HAVE_DYNAMIC_FTRACE
	select HAVE_DYNAMIC_FTRACE_WITH_REGS

endchoice

+3 −0
Original line number Diff line number Diff line
@@ -11,6 +11,9 @@
LDFLAGS         :=
OBJCOPYFLAGS    := -O binary
LDFLAGS_vmlinux :=
ifeq ($(CONFIG_DYNAMIC_FTRACE),y)
	LDFLAGS_vmlinux := --no-relax
endif
KBUILD_AFLAGS_MODULE += -fPIC
KBUILD_CFLAGS_MODULE += -fPIC

+56 −0
Original line number Diff line number Diff line
@@ -8,3 +8,59 @@
#if defined(CONFIG_FUNCTION_GRAPH_TRACER) && defined(CONFIG_FRAME_POINTER)
#define HAVE_FUNCTION_GRAPH_FP_TEST
#endif
#define HAVE_FUNCTION_GRAPH_RET_ADDR_PTR

#define ARCH_SUPPORTS_FTRACE_OPS 1
#ifndef __ASSEMBLY__
void _mcount(void);
static inline unsigned long ftrace_call_adjust(unsigned long addr)
{
	return addr;
}

struct dyn_arch_ftrace {
};
#endif

#ifdef CONFIG_DYNAMIC_FTRACE
/*
 * A general call in RISC-V is a pair of insts:
 * 1) auipc: setting high-20 pc-related bits to ra register
 * 2) jalr: setting low-12 offset to ra, jump to ra, and set ra to
 *          return address (original pc + 4)
 *
 * Dynamic ftrace generates probes to call sites, so we must deal with
 * both auipc and jalr at the same time.
 */

#define MCOUNT_ADDR		((unsigned long)_mcount)
#define JALR_SIGN_MASK		(0x00000800)
#define JALR_OFFSET_MASK	(0x00000fff)
#define AUIPC_OFFSET_MASK	(0xfffff000)
#define AUIPC_PAD		(0x00001000)
#define JALR_SHIFT		20
#define JALR_BASIC		(0x000080e7)
#define AUIPC_BASIC		(0x00000097)
#define NOP4			(0x00000013)

#define make_call(caller, callee, call)					\
do {									\
	call[0] = to_auipc_insn((unsigned int)((unsigned long)callee -	\
				(unsigned long)caller));		\
	call[1] = to_jalr_insn((unsigned int)((unsigned long)callee -	\
			       (unsigned long)caller));			\
} while (0)

#define to_jalr_insn(offset)						\
	(((offset & JALR_OFFSET_MASK) << JALR_SHIFT) | JALR_BASIC)

#define to_auipc_insn(offset)						\
	((offset & JALR_SIGN_MASK) ?					\
	(((offset & AUIPC_OFFSET_MASK) + AUIPC_PAD) | AUIPC_BASIC) :	\
	((offset & AUIPC_OFFSET_MASK) | AUIPC_BASIC))

/*
 * Let auipc+jalr be the basic *mcount unit*, so we make it 8 bytes here.
 */
#define MCOUNT_INSN_SIZE 8
#endif
+3 −2
Original line number Diff line number Diff line
@@ -34,7 +34,8 @@ CFLAGS_setup.o := -mcmodel=medany
obj-$(CONFIG_SMP)		+= smpboot.o
obj-$(CONFIG_SMP)		+= smp.o
obj-$(CONFIG_MODULES)		+= module.o
obj-$(CONFIG_FUNCTION_TRACER)	+= mcount.o
obj-$(CONFIG_FUNCTION_GRAPH_TRACER)	+= ftrace.o

obj-$(CONFIG_FUNCTION_TRACER)	+= mcount.o ftrace.o
obj-$(CONFIG_DYNAMIC_FTRACE)	+= mcount-dyn.o

clean:
+173 −2
Original line number Diff line number Diff line
@@ -6,9 +6,126 @@
 */

#include <linux/ftrace.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>

#ifdef CONFIG_DYNAMIC_FTRACE
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 (probe_kernel_read(replaced, (void *)hook_pos, MCOUNT_INSN_SIZE))
		return -EFAULT;

	/*
 * Most of this file is copied from arm64.
	 * 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 get (%08x %08x)",
		       (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)
{
	unsigned int call[2];
	unsigned int nops[2] = {NOP4, NOP4};
	int ret = 0;

	make_call(hook_pos, target, call);

	/* replace the auipc-jalr pair at once */
	ret = probe_kernel_write((void *)hook_pos, enable ? call : nops,
				 MCOUNT_INSN_SIZE);
	/* return must be -EPERM on write error */
	if (ret)
		return -EPERM;

	smp_mb();
	flush_icache_range((void *)hook_pos, (void *)hook_pos + MCOUNT_INSN_SIZE);

	return 0;
}

int ftrace_make_call(struct dyn_ftrace *rec, unsigned long addr)
{
	int ret = ftrace_check_current_call(rec->ip, NULL);

	if (ret)
		return ret;

	return __ftrace_modify_call(rec->ip, addr, true);
}

int ftrace_make_nop(struct module *mod, struct dyn_ftrace *rec,
		    unsigned long addr)
{
	unsigned int call[2];
	int ret;

	make_call(rec->ip, addr, call);
	ret = ftrace_check_current_call(rec->ip, call);

	if (ret)
		return ret;

	return __ftrace_modify_call(rec->ip, addr, false);
}

int ftrace_update_ftrace_func(ftrace_func_t func)
{
	int ret = __ftrace_modify_call((unsigned long)&ftrace_call,
				       (unsigned long)func, true);
	if (!ret) {
		ret = __ftrace_modify_call((unsigned long)&ftrace_regs_call,
					   (unsigned long)func, true);
	}

	return ret;
}

int __init ftrace_dyn_arch_init(void)
{
	return 0;
}
#endif

#ifdef CONFIG_DYNAMIC_FTRACE_WITH_REGS
int ftrace_modify_call(struct dyn_ftrace *rec, unsigned long old_addr,
		       unsigned long addr)
{
	unsigned int call[2];
	int ret;

	make_call(rec->ip, old_addr, call);
	ret = ftrace_check_current_call(rec->ip, call);

	if (ret)
		return ret;

	return __ftrace_modify_call(rec->ip, addr, true);
}
#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)
@@ -34,8 +151,62 @@ void prepare_ftrace_return(unsigned long *parent, unsigned long self_addr,
		return;

	err = ftrace_push_return_trace(old, self_addr, &trace.depth,
				       frame_pointer, NULL);
				       frame_pointer, parent);
	if (err == -EBUSY)
		return;
	*parent = return_hooker;
}

#ifdef CONFIG_DYNAMIC_FTRACE
extern void ftrace_graph_call(void);
int ftrace_enable_ftrace_graph_caller(void)
{
	unsigned int call[2];
	static int init_graph = 1;
	int ret;

	make_call(&ftrace_graph_call, &ftrace_stub, call);

	/*
	 * When enabling graph tracer for the first time, ftrace_graph_call
	 * should contains a call to ftrace_stub.  Once it has been disabled,
	 * the 8-bytes at the position becomes NOPs.
	 */
	if (init_graph) {
		ret = ftrace_check_current_call((unsigned long)&ftrace_graph_call,
						call);
		init_graph = 0;
	} else {
		ret = ftrace_check_current_call((unsigned long)&ftrace_graph_call,
						NULL);
	}

	if (ret)
		return ret;

	return __ftrace_modify_call((unsigned long)&ftrace_graph_call,
				    (unsigned long)&prepare_ftrace_return, true);
}

int ftrace_disable_ftrace_graph_caller(void)
{
	unsigned int call[2];
	int ret;

	make_call(&ftrace_graph_call, &prepare_ftrace_return, call);

	/*
	 * This is to make sure that ftrace_enable_ftrace_graph_caller
	 * did the right thing.
	 */
	ret = ftrace_check_current_call((unsigned long)&ftrace_graph_call,
					call);

	if (ret)
		return ret;

	return __ftrace_modify_call((unsigned long)&ftrace_graph_call,
				    (unsigned long)&prepare_ftrace_return, false);
}
#endif /* CONFIG_DYNAMIC_FTRACE */
#endif /* CONFIG_FUNCTION_GRAPH_TRACER */
Loading