summaryrefslogtreecommitdiff
path: root/arch/arm/oprofile/common.c
blob: 485e58bf7f4941a342ba773b7066fda3c428a317 (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
/**
 * @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
char *op_name_from_perf_id(void)
{
	enum arm_perf_pmu_ids id = armpmu_get_pmu_id();

	switch (id) {
	case ARM_PERF_PMU_ID_XSCALE1:
		return "arm/xscale1";
	case ARM_PERF_PMU_ID_XSCALE2:
		return "arm/xscale2";
	case ARM_PERF_PMU_ID_V6:
		return "arm/armv6";
	case ARM_PERF_PMU_ID_V6MP:
		return "arm/mpcore";
	case ARM_PERF_PMU_ID_CA8:
		return "arm/armv7";
	case ARM_PERF_PMU_ID_CA9:
		return "arm/armv7-ca9";
	default:
		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();
}