aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ubicom32/files/arch/ubicom32/oprofile/profile.c
blob: aeac3c6667dcdfd682d1ed17d99db05e17ce9a00 (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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/*
 * arch/ubicom32/oprofile/profile.c
 *	Oprofile support for arch Ubicom32
 *
 * (C) Copyright 2009, Ubicom, Inc.
 *
 * This file is part of the Ubicom32 Linux Kernel Port.
 *
 * The Ubicom32 Linux Kernel Port is free software: you can redistribute
 * it and/or modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation, either version 2 of the
 * License, or (at your option)
 * any later version.
 *
 * The Ubicom32 Linux Kernel Port is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Ubicom32 Linux Kernel Port.  If not, see
 * <http://www.gnu.org/licenses/>.
 *
 * Ubicom32 implementation derived from (with many thanks):
 *   arch/m68knommu
 *   arch/blackfin
 *   arch/parisc
 */

/**
 * @file profile.c
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author Hunyue Yau <hy@hy-research.com>
 */

#include <linux/oprofile.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <asm/devtree.h>
#include <asm/thread.h>

/* For identifying userland vs kernel address */
#include <asm/stacktrace.h>
#include "ipProf.h"

/* For communications with the backend */
static struct profilenode *profile_node;

/* Bitmask containing all Linux threads - as seen by the ROSR reg */
static unsigned long th_all_mask;

/* Lookup table to translate a hardware thread into a CPU identifier
 * Table is indexed by the ROSR value which is assumed to be
 * relatively small (0...15).
 */
unsigned int cpu_map[THREAD_ARCHITECTURAL_MAX];

static struct pt_regs regs;

/*
 * For each sample returned, checked to see if they are relevant to
 * us. This is necessary as the ubicom32 architecture has other software
 * running outside of Linux. Only then, put the sample into the relevant
 * cpu bins.
 *
 * To minimize overhead, a global mask with all possible threads of in
 * interest to us is used as a first check. Then a second mask identifying
 * the thread is used to obtain an identifier for that "CPU".
 */

/*
 * ubicom32_build_cpu_th_mask()
 *
 * Build a lookup table for translation between hardware thread
 * "ROSR" values and Linux CPU ids
 *
 * *** This gets executed on all CPUs at once! ***
 */
static void ubicom32_build_cpu_th_mask(void *mask)
{
	thread_t self = thread_get_self();
	unsigned long *th_m = mask;

	BUG_ON(self <= 0 || self >= THREAD_ARCHITECTURAL_MAX);
	cpu_map[self] = smp_processor_id();

	set_bit(self, th_m);
}

/*
 * profile_interrupt()
 *
 * Process samples returned from the profiler backend. The backend
 * may return samples that are irrelevant to us or may even return
 * multiple samples for the same CPU. Note that the sames may be
 * for ANY cpu. At this time, this is unique and to support this requires
 * Oprofile to expose an interface to accept the CPU that the same came
 * frome.
 */
static irqreturn_t profile_interrupt(int irq, void *arg)
{
	int i, buf_entry;
	int is_kernel;
	unsigned int bit_th;
	unsigned int th;

	if (!(profile_node->enabled) || profile_node->count < 0) {
		printk(KERN_WARNING
			"Unexpected interrupt, no samples or not enabled!\n");
		return IRQ_HANDLED;
	}

	profile_node->busy = 1;		/* Keep backend out */

	for (i = 0; i < profile_node->count; i++) {
		buf_entry = profile_node->tail;
		profile_node->tail++;
		profile_node->tail %= IPPROFILETIO_MAX_SAMPLES;

		/* Note - the "thread" ID is only the lower 4 bits */
		th = (0x0f & profile_node->samples[buf_entry].thread);
		bit_th = (1 << th);

		if ((bit_th & th_all_mask) == 0)
			continue;

		regs.pc = profile_node->samples[buf_entry].pc;

		is_kernel = ubicom32_is_kernel(regs.pc);

		oprofile_add_ext_sample_cpu(regs.pc, &regs, 0, is_kernel,
					    cpu_map[th]);
	}
	profile_node->count = 0;
	profile_node->busy = 0;

	return IRQ_HANDLED;
}

/*
 * profile_start()
 *
 * Notification from oprofile to start the profiler
 */
static int profile_start(void)
{
	if (!profile_node)
		return -1;

	profile_node->enabled = 1;

	return 0;
}

/*
 * profile_stop()
 *
 * Notification from oprofile to stop the profiler
 */
static void profile_stop(void)
{
	if (profile_node)
		profile_node->enabled = 0;
}

/*
 * oprofile_arch_init()
 *
 * Attach to Oprofile after qualify the availability of the backend
 * profiler support.
 */
int __init oprofile_arch_init(struct oprofile_operations *ops)
{
	int r = -ENODEV;

	profile_node = (struct profilenode *)devtree_find_node("profiler");

	if (profile_node == NULL) {
		printk(KERN_WARNING "Cannot find profiler node\n");
		return r;
	}

	r = request_irq(profile_node->dn.recvirq, profile_interrupt,
			IRQF_DISABLED, "profiler", NULL);

	if (r < 0) {
		profile_node = NULL;
		printk(KERN_WARNING "Cannot get profiler IRQ\n");
		return r;
	}

	ops->start = profile_start;
	ops->stop = profile_stop;
	ops->cpu_type = "timer";

	memset(cpu_map, 0, sizeof(cpu_map));

	on_each_cpu(ubicom32_build_cpu_th_mask, &th_all_mask, 1);

	memset(&regs, 0, sizeof(regs));

	return r;
}

/*
 * oprofile_arch_exit()
 *
 * External call to take outselves out.
 * Make sure backend is not running.
 */
void oprofile_arch_exit(void)
{
	BUG_ON(profile_node->enabled);
}