summaryrefslogtreecommitdiff
path: root/arch/arm/oprofile/common.c
blob: 7c6fcd3e5e6143cbbcb7036a8cb612ca530f570c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
/**
 * @file common.c
 *
 * @remark Copyright 2004 Oprofile Authors
 * @remark Copyright 2010 ARM Ltd.
 * @remark Read the file COPYING
 *
 * @author Zwane Mwaikambo
 * @author Will Deacon [move to perf]
 */

#include <linux/cpumask.h>
#include <linux/init.h>
#include <linux/mutex.h>
#include <linux/oprofile.h>
#include <linux/perf_event.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/stacktrace.h>
#include <linux/uaccess.h>

#include <asm/perf_event.h>
#include <asm/ptrace.h>

#ifdef CONFIG_HW_PERF_EVENTS

/*
 * OProfile has a curious naming scheme for the ARM PMUs, but they are
 * part of the user ABI so we need to map from the perf PMU name for
 * supported PMUs.
 */
static struct op_perf_name {
	char *perf_name;
	char *op_name;
} op_perf_name_map[] = {
	{ "xscale1",		"arm/xscale1"	},
	{ "xscale1",		"arm/xscale2"	},
	{ "v6",			"arm/armv6"	},
	{ "v6mpcore",		"arm/mpcore"	},
	{ "ARMv7 Cortex-A8",	"arm/armv7"	},
	{ "ARMv7 Cortex-A9",	"arm/armv7-ca9"	},
};

char *op_name_from_perf_id(void)
{
	int i;
	struct op_perf_name names;
	const char *perf_name = perf_pmu_name();

	for (i = 0; i < ARRAY_SIZE(op_perf_name_map); ++i) {
		names = op_perf_name_map[i];
		if (!strcmp(names.perf_name, perf_name))
			return names.op_name;
	}

	return NULL;
}
#endif

static int report_trace(struct stackframe *frame, void *d)
{
	unsigned int *depth = d;

	if (*depth) {
		oprofile_add_trace(frame->pc);
		(*depth)--;
	}

	return *depth == 0;
}

#ifdef CONFIG_ANDROID
/* Android has a different stack frame than Linux */
struct frame_tail {
	unsigned long fp;
	unsigned long lr;
} __attribute__((packed));
#else
/*
 * The registers we're interested in are at the end of the variable
 * length saved register structure. The fp points at the end of this
 * structure so the address of this struct is:
 * (struct frame_tail *)(xxx->fp)-1
 */
struct frame_tail {
	struct frame_tail *fp;
	unsigned long sp;
	unsigned long lr;
} __attribute__((packed));
#endif

static struct frame_tail* user_backtrace(struct frame_tail *tail)
{
	struct frame_tail buftail[2];

	/* Also check accessibility of one struct frame_tail beyond */
	if (!access_ok(VERIFY_READ, tail, sizeof(buftail)))
		return NULL;
	if (__copy_from_user_inatomic(buftail, tail, sizeof(buftail)))
		return NULL;

	oprofile_add_trace(buftail[0].lr);

	/* frame pointers should strictly progress back up the stack
	 * (towards higher addresses) */
#ifdef CONFIG_ANDROID
	if (tail >= (struct frame_tail *) buftail[0].fp)
#else
	if (tail + 1 >= buftail[0].fp)
#endif
		return NULL;

#ifdef CONFIG_ANDROID
	/* Android has a different stack frame than Linux */
	return (struct frame_tail *) (buftail[0].fp - sizeof(unsigned long));
#else
	return buftail[0].fp-1;
#endif
}

static void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
{
#ifdef CONFIG_ANDROID
	struct frame_tail *tail = (struct frame_tail *)
	                          (regs->ARM_fp - sizeof(unsigned long));
#else
	struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;
#endif

	if (!user_mode(regs)) {
		struct stackframe frame;
		frame.fp = regs->ARM_fp;
		frame.sp = regs->ARM_sp;
		frame.lr = regs->ARM_lr;
		frame.pc = regs->ARM_pc;
		walk_stackframe(&frame, report_trace, &depth);
		return;
	}

	while (depth-- && tail && !((unsigned long) tail & 3))
		tail = user_backtrace(tail);
}

int __init oprofile_arch_init(struct oprofile_operations *ops)
{
	/* provide backtrace support also in timer mode: */
	ops->backtrace		= arm_backtrace;

	return oprofile_perf_init(ops);
}

void oprofile_arch_exit(void)
{
	oprofile_perf_exit();
}