xref: /linux/arch/csky/kernel/perf_callchain.c (revision 03ab8e6297acd1bc0eedaa050e2a1635c576fd11)
1cfa4d93bSMao Han // SPDX-License-Identifier: GPL-2.0
2cfa4d93bSMao Han // Copyright (C) 2019 Hangzhou C-SKY Microsystems co.,ltd.
3cfa4d93bSMao Han 
4cfa4d93bSMao Han #include <linux/perf_event.h>
5cfa4d93bSMao Han #include <linux/uaccess.h>
6cfa4d93bSMao Han 
7cfa4d93bSMao Han /* Kernel callchain */
8cfa4d93bSMao Han struct stackframe {
9cfa4d93bSMao Han 	unsigned long fp;
10cfa4d93bSMao Han 	unsigned long lr;
11cfa4d93bSMao Han };
12cfa4d93bSMao Han 
13cfa4d93bSMao Han static int unwind_frame_kernel(struct stackframe *frame)
14cfa4d93bSMao Han {
15229a0ddeSMao Han 	unsigned long low = (unsigned long)task_stack_page(current);
16229a0ddeSMao Han 	unsigned long high = low + THREAD_SIZE;
17229a0ddeSMao Han 
18229a0ddeSMao Han 	if (unlikely(frame->fp < low || frame->fp > high))
19cfa4d93bSMao Han 		return -EPERM;
20229a0ddeSMao Han 
21229a0ddeSMao Han 	if (kstack_end((void *)frame->fp) || frame->fp & 0x3)
22cfa4d93bSMao Han 		return -EPERM;
23cfa4d93bSMao Han 
24cfa4d93bSMao Han 	*frame = *(struct stackframe *)frame->fp;
25229a0ddeSMao Han 
26cfa4d93bSMao Han 	if (__kernel_text_address(frame->lr)) {
27cfa4d93bSMao Han 		int graph = 0;
28cfa4d93bSMao Han 
29cfa4d93bSMao Han 		frame->lr = ftrace_graph_ret_addr(NULL, &graph, frame->lr,
30cfa4d93bSMao Han 				NULL);
31cfa4d93bSMao Han 	}
32cfa4d93bSMao Han 	return 0;
33cfa4d93bSMao Han }
34cfa4d93bSMao Han 
35cfa4d93bSMao Han static void notrace walk_stackframe(struct stackframe *fr,
36cfa4d93bSMao Han 			struct perf_callchain_entry_ctx *entry)
37cfa4d93bSMao Han {
38cfa4d93bSMao Han 	do {
39cfa4d93bSMao Han 		perf_callchain_store(entry, fr->lr);
40cfa4d93bSMao Han 	} while (unwind_frame_kernel(fr) >= 0);
41cfa4d93bSMao Han }
42cfa4d93bSMao Han 
43cfa4d93bSMao Han /*
44cfa4d93bSMao Han  * Get the return address for a single stackframe and return a pointer to the
45cfa4d93bSMao Han  * next frame tail.
46cfa4d93bSMao Han  */
47cfa4d93bSMao Han static unsigned long user_backtrace(struct perf_callchain_entry_ctx *entry,
48cfa4d93bSMao Han 			unsigned long fp, unsigned long reg_lr)
49cfa4d93bSMao Han {
50cfa4d93bSMao Han 	struct stackframe buftail;
51cfa4d93bSMao Han 	unsigned long lr = 0;
5223fc539eSArnd Bergmann 	unsigned long __user *user_frame_tail = (unsigned long __user *)fp;
53cfa4d93bSMao Han 
54cfa4d93bSMao Han 	/* Check accessibility of one struct frame_tail beyond */
55cfa4d93bSMao Han 	if (!access_ok(user_frame_tail, sizeof(buftail)))
56cfa4d93bSMao Han 		return 0;
57cfa4d93bSMao Han 	if (__copy_from_user_inatomic(&buftail, user_frame_tail,
58cfa4d93bSMao Han 				      sizeof(buftail)))
59cfa4d93bSMao Han 		return 0;
60cfa4d93bSMao Han 
61cfa4d93bSMao Han 	if (reg_lr != 0)
62cfa4d93bSMao Han 		lr = reg_lr;
63cfa4d93bSMao Han 	else
64cfa4d93bSMao Han 		lr = buftail.lr;
65cfa4d93bSMao Han 
66cfa4d93bSMao Han 	fp = buftail.fp;
67cfa4d93bSMao Han 	perf_callchain_store(entry, lr);
68cfa4d93bSMao Han 
69cfa4d93bSMao Han 	return fp;
70cfa4d93bSMao Han }
71cfa4d93bSMao Han 
72cfa4d93bSMao Han /*
73cfa4d93bSMao Han  * This will be called when the target is in user mode
74cfa4d93bSMao Han  * This function will only be called when we use
75cfa4d93bSMao Han  * "PERF_SAMPLE_CALLCHAIN" in
76cfa4d93bSMao Han  * kernel/events/core.c:perf_prepare_sample()
77cfa4d93bSMao Han  *
78cfa4d93bSMao Han  * How to trigger perf_callchain_[user/kernel] :
79cfa4d93bSMao Han  * $ perf record -e cpu-clock --call-graph fp ./program
80cfa4d93bSMao Han  * $ perf report --call-graph
81cfa4d93bSMao Han  *
82cfa4d93bSMao Han  * On C-SKY platform, the program being sampled and the C library
83cfa4d93bSMao Han  * need to be compiled with * -mbacktrace, otherwise the user
84cfa4d93bSMao Han  * stack will not contain function frame.
85cfa4d93bSMao Han  */
86cfa4d93bSMao Han void perf_callchain_user(struct perf_callchain_entry_ctx *entry,
87cfa4d93bSMao Han 			 struct pt_regs *regs)
88cfa4d93bSMao Han {
89cfa4d93bSMao Han 	unsigned long fp = 0;
90cfa4d93bSMao Han 
91cfa4d93bSMao Han 	fp = regs->regs[4];
92cfa4d93bSMao Han 	perf_callchain_store(entry, regs->pc);
93cfa4d93bSMao Han 
94cfa4d93bSMao Han 	/*
95cfa4d93bSMao Han 	 * While backtrace from leaf function, lr is normally
96cfa4d93bSMao Han 	 * not saved inside frame on C-SKY, so get lr from pt_regs
97cfa4d93bSMao Han 	 * at the sample point. However, lr value can be incorrect if
98cfa4d93bSMao Han 	 * lr is used as temp register
99cfa4d93bSMao Han 	 */
100cfa4d93bSMao Han 	fp = user_backtrace(entry, fp, regs->lr);
101cfa4d93bSMao Han 
102cfa4d93bSMao Han 	while (fp && !(fp & 0x3) && entry->nr < entry->max_stack)
103cfa4d93bSMao Han 		fp = user_backtrace(entry, fp, 0);
104cfa4d93bSMao Han }
105cfa4d93bSMao Han 
106cfa4d93bSMao Han void perf_callchain_kernel(struct perf_callchain_entry_ctx *entry,
107cfa4d93bSMao Han 			   struct pt_regs *regs)
108cfa4d93bSMao Han {
109cfa4d93bSMao Han 	struct stackframe fr;
110cfa4d93bSMao Han 
111cfa4d93bSMao Han 	fr.fp = regs->regs[4];
112cfa4d93bSMao Han 	fr.lr = regs->lr;
113cfa4d93bSMao Han 	walk_stackframe(&fr, entry);
114cfa4d93bSMao Han }
115