xref: /openbmc/linux/arch/arm/kernel/perf_callchain.c (revision b24413180f5600bcb3bb70fbed5cf186b60864bd)
1*b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2d39976f0SMark Rutland /*
3d39976f0SMark Rutland  * ARM callchain support
4d39976f0SMark Rutland  *
5d39976f0SMark Rutland  * Copyright (C) 2009 picoChip Designs, Ltd., Jamie Iles
6d39976f0SMark Rutland  * Copyright (C) 2010 ARM Ltd., Will Deacon <will.deacon@arm.com>
7d39976f0SMark Rutland  *
8d39976f0SMark Rutland  * This code is based on the ARM OProfile backtrace code.
9d39976f0SMark Rutland  */
10d39976f0SMark Rutland #include <linux/perf_event.h>
11d39976f0SMark Rutland #include <linux/uaccess.h>
12d39976f0SMark Rutland 
13d39976f0SMark Rutland #include <asm/stacktrace.h>
14d39976f0SMark Rutland 
15d39976f0SMark Rutland /*
16d39976f0SMark Rutland  * The registers we're interested in are at the end of the variable
17d39976f0SMark Rutland  * length saved register structure. The fp points at the end of this
18d39976f0SMark Rutland  * structure so the address of this struct is:
19d39976f0SMark Rutland  * (struct frame_tail *)(xxx->fp)-1
20d39976f0SMark Rutland  *
21d39976f0SMark Rutland  * This code has been adapted from the ARM OProfile support.
22d39976f0SMark Rutland  */
23d39976f0SMark Rutland struct frame_tail {
24d39976f0SMark Rutland 	struct frame_tail __user *fp;
25d39976f0SMark Rutland 	unsigned long sp;
26d39976f0SMark Rutland 	unsigned long lr;
27d39976f0SMark Rutland } __attribute__((packed));
28d39976f0SMark Rutland 
29d39976f0SMark Rutland /*
30d39976f0SMark Rutland  * Get the return address for a single stackframe and return a pointer to the
31d39976f0SMark Rutland  * next frame tail.
32d39976f0SMark Rutland  */
33d39976f0SMark Rutland static struct frame_tail __user *
34d39976f0SMark Rutland user_backtrace(struct frame_tail __user *tail,
35cfbcf468SArnaldo Carvalho de Melo 	       struct perf_callchain_entry_ctx *entry)
36d39976f0SMark Rutland {
37d39976f0SMark Rutland 	struct frame_tail buftail;
38d39976f0SMark Rutland 	unsigned long err;
39d39976f0SMark Rutland 
40d39976f0SMark Rutland 	if (!access_ok(VERIFY_READ, tail, sizeof(buftail)))
41d39976f0SMark Rutland 		return NULL;
42d39976f0SMark Rutland 
43d39976f0SMark Rutland 	pagefault_disable();
44d39976f0SMark Rutland 	err = __copy_from_user_inatomic(&buftail, tail, sizeof(buftail));
45d39976f0SMark Rutland 	pagefault_enable();
46d39976f0SMark Rutland 
47d39976f0SMark Rutland 	if (err)
48d39976f0SMark Rutland 		return NULL;
49d39976f0SMark Rutland 
50d39976f0SMark Rutland 	perf_callchain_store(entry, buftail.lr);
51d39976f0SMark Rutland 
52d39976f0SMark Rutland 	/*
53d39976f0SMark Rutland 	 * Frame pointers should strictly progress back up the stack
54d39976f0SMark Rutland 	 * (towards higher addresses).
55d39976f0SMark Rutland 	 */
56d39976f0SMark Rutland 	if (tail + 1 >= buftail.fp)
57d39976f0SMark Rutland 		return NULL;
58d39976f0SMark Rutland 
59d39976f0SMark Rutland 	return buftail.fp - 1;
60d39976f0SMark Rutland }
61d39976f0SMark Rutland 
62d39976f0SMark Rutland void
63cfbcf468SArnaldo Carvalho de Melo perf_callchain_user(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs)
64d39976f0SMark Rutland {
65d39976f0SMark Rutland 	struct frame_tail __user *tail;
66d39976f0SMark Rutland 
67d39976f0SMark Rutland 	if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {
68d39976f0SMark Rutland 		/* We don't support guest os callchain now */
69d39976f0SMark Rutland 		return;
70d39976f0SMark Rutland 	}
71d39976f0SMark Rutland 
72d39976f0SMark Rutland 	perf_callchain_store(entry, regs->ARM_pc);
73d39976f0SMark Rutland 
74d39976f0SMark Rutland 	if (!current->mm)
75d39976f0SMark Rutland 		return;
76d39976f0SMark Rutland 
77d39976f0SMark Rutland 	tail = (struct frame_tail __user *)regs->ARM_fp - 1;
78d39976f0SMark Rutland 
793b1fff08SArnaldo Carvalho de Melo 	while ((entry->nr < entry->max_stack) &&
80d39976f0SMark Rutland 	       tail && !((unsigned long)tail & 0x3))
81d39976f0SMark Rutland 		tail = user_backtrace(tail, entry);
82d39976f0SMark Rutland }
83d39976f0SMark Rutland 
84d39976f0SMark Rutland /*
85d39976f0SMark Rutland  * Gets called by walk_stackframe() for every stackframe. This will be called
86d39976f0SMark Rutland  * whist unwinding the stackframe and is like a subroutine return so we use
87d39976f0SMark Rutland  * the PC.
88d39976f0SMark Rutland  */
89d39976f0SMark Rutland static int
90d39976f0SMark Rutland callchain_trace(struct stackframe *fr,
91d39976f0SMark Rutland 		void *data)
92d39976f0SMark Rutland {
93cfbcf468SArnaldo Carvalho de Melo 	struct perf_callchain_entry_ctx *entry = data;
94d39976f0SMark Rutland 	perf_callchain_store(entry, fr->pc);
95d39976f0SMark Rutland 	return 0;
96d39976f0SMark Rutland }
97d39976f0SMark Rutland 
98d39976f0SMark Rutland void
99cfbcf468SArnaldo Carvalho de Melo perf_callchain_kernel(struct perf_callchain_entry_ctx *entry, struct pt_regs *regs)
100d39976f0SMark Rutland {
101d39976f0SMark Rutland 	struct stackframe fr;
102d39976f0SMark Rutland 
103d39976f0SMark Rutland 	if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {
104d39976f0SMark Rutland 		/* We don't support guest os callchain now */
105d39976f0SMark Rutland 		return;
106d39976f0SMark Rutland 	}
107d39976f0SMark Rutland 
108d39976f0SMark Rutland 	arm_get_current_stackframe(regs, &fr);
109d39976f0SMark Rutland 	walk_stackframe(&fr, callchain_trace, entry);
110d39976f0SMark Rutland }
111d39976f0SMark Rutland 
112d39976f0SMark Rutland unsigned long perf_instruction_pointer(struct pt_regs *regs)
113d39976f0SMark Rutland {
114d39976f0SMark Rutland 	if (perf_guest_cbs && perf_guest_cbs->is_in_guest())
115d39976f0SMark Rutland 		return perf_guest_cbs->get_guest_ip();
116d39976f0SMark Rutland 
117d39976f0SMark Rutland 	return instruction_pointer(regs);
118d39976f0SMark Rutland }
119d39976f0SMark Rutland 
120d39976f0SMark Rutland unsigned long perf_misc_flags(struct pt_regs *regs)
121d39976f0SMark Rutland {
122d39976f0SMark Rutland 	int misc = 0;
123d39976f0SMark Rutland 
124d39976f0SMark Rutland 	if (perf_guest_cbs && perf_guest_cbs->is_in_guest()) {
125d39976f0SMark Rutland 		if (perf_guest_cbs->is_user_mode())
126d39976f0SMark Rutland 			misc |= PERF_RECORD_MISC_GUEST_USER;
127d39976f0SMark Rutland 		else
128d39976f0SMark Rutland 			misc |= PERF_RECORD_MISC_GUEST_KERNEL;
129d39976f0SMark Rutland 	} else {
130d39976f0SMark Rutland 		if (user_mode(regs))
131d39976f0SMark Rutland 			misc |= PERF_RECORD_MISC_USER;
132d39976f0SMark Rutland 		else
133d39976f0SMark Rutland 			misc |= PERF_RECORD_MISC_KERNEL;
134d39976f0SMark Rutland 	}
135d39976f0SMark Rutland 
136d39976f0SMark Rutland 	return misc;
137d39976f0SMark Rutland }
138