summaryrefslogtreecommitdiffstats
path: root/kernel/arch/arm/oprofile/common.c
blob: cc649a1e46da27ae45488d743597edcc251430ff (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
/**
 * @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[] = {
	{ "armv5_xscale1",	"arm/xscale1"	},
	{ "armv5_xscale2",	"arm/xscale2"	},
	{ "armv6_1136",		"arm/armv6"	},
	{ "armv6_1156",		"arm/armv6"	},
	{ "armv6_1176",		"arm/armv6"	},
	{ "armv6_11mpcore",	"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;
}

/*
 * 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));

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) */
	if (tail + 1 >= buftail[0].fp)
		return NULL;

	return buftail[0].fp-1;
}

static void arm_backtrace(struct pt_regs * const regs, unsigned int depth)
{
	struct frame_tail *tail = ((struct frame_tail *) regs->ARM_fp) - 1;

	if (!user_mode(regs)) {
		struct stackframe frame;
		arm_get_current_stackframe(regs, &frame);
		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();
}