1/**
2 * @file arch/alpha/oprofile/op_model_ev67.c
3 *
4 * @remark Copyright 2002 OProfile authors
5 * @remark Read the file COPYING
6 *
7 * @author Richard Henderson <rth@twiddle.net>
8 * @author Falk Hueffner <falk@debian.org>
9 */
10
11#include <linux/oprofile.h>
12#include <linux/smp.h>
13#include <asm/ptrace.h>
14
15#include "op_impl.h"
16
17
18/* Compute all of the registers in preparation for enabling profiling.  */
19
20static void
21ev67_reg_setup(struct op_register_config *reg,
22	       struct op_counter_config *ctr,
23	       struct op_system_config *sys)
24{
25	unsigned long ctl, reset, need_reset, i;
26
27	/* Select desired events.  */
28	ctl = 1UL << 4;		/* Enable ProfileMe mode. */
29
30	/* The event numbers are chosen so we can use them directly if
31	   PCTR1 is enabled.  */
32	if (ctr[1].enabled) {
33		ctl |= (ctr[1].event & 3) << 2;
34	} else {
35		if (ctr[0].event == 0) /* cycles */
36			ctl |= 1UL << 2;
37	}
38	reg->mux_select = ctl;
39
40	/* Select logging options.  */
41	/* ??? Need to come up with some mechanism to trace only
42	   selected processes.  EV67 does not have a mechanism to
43	   select kernel or user mode only.  For now, enable always.  */
44	reg->proc_mode = 0;
45
46	/* EV67 cannot change the width of the counters as with the
47	   other implementations.  But fortunately, we can write to
48	   the counters and set the value such that it will overflow
49	   at the right time.  */
50	reset = need_reset = 0;
51	for (i = 0; i < 2; ++i) {
52		unsigned long count = ctr[i].count;
53		if (!ctr[i].enabled)
54			continue;
55
56		if (count > 0x100000)
57			count = 0x100000;
58		ctr[i].count = count;
59		reset |= (0x100000 - count) << (i ? 6 : 28);
60		if (count != 0x100000)
61			need_reset |= 1 << i;
62	}
63	reg->reset_values = reset;
64	reg->need_reset = need_reset;
65}
66
67/* Program all of the registers in preparation for enabling profiling.  */
68
69static void
70ev67_cpu_setup (void *x)
71{
72	struct op_register_config *reg = x;
73
74	wrperfmon(2, reg->mux_select);
75	wrperfmon(3, reg->proc_mode);
76	wrperfmon(6, reg->reset_values | 3);
77}
78
79/* CTR is a counter for which the user has requested an interrupt count
80   in between one of the widths selectable in hardware.  Reset the count
81   for CTR to the value stored in REG->RESET_VALUES.  */
82
83static void
84ev67_reset_ctr(struct op_register_config *reg, unsigned long ctr)
85{
86	wrperfmon(6, reg->reset_values | (1 << ctr));
87}
88
89/* ProfileMe conditions which will show up as counters. We can also
90   detect the following, but it seems unlikely that anybody is
91   interested in counting them:
92    * Reset
93    * MT_FPCR (write to floating point control register)
94    * Arithmetic trap
95    * Dstream Fault
96    * Machine Check (ECC fault, etc.)
97    * OPCDEC (illegal opcode)
98    * Floating point disabled
99    * Differentiate between DTB single/double misses and 3 or 4 level
100      page tables
101    * Istream access violation
102    * Interrupt
103    * Icache Parity Error.
104    * Instruction killed (nop, trapb)
105
106   Unfortunately, there seems to be no way to detect Dcache and Bcache
107   misses; the latter could be approximated by making the counter
108   count Bcache misses, but that is not precise.
109
110   We model this as 20 counters:
111    * PCTR0
112    * PCTR1
113    * 9 ProfileMe events, induced by PCTR0
114    * 9 ProfileMe events, induced by PCTR1
115*/
116
117enum profileme_counters {
118	PM_STALLED,		/* Stalled for at least one cycle
119				   between the fetch and map stages  */
120	PM_TAKEN,		/* Conditional branch taken */
121	PM_MISPREDICT,		/* Branch caused mispredict trap */
122	PM_ITB_MISS,		/* ITB miss */
123	PM_DTB_MISS,		/* DTB miss */
124	PM_REPLAY,		/* Replay trap */
125	PM_LOAD_STORE,		/* Load-store order trap */
126	PM_ICACHE_MISS,		/* Icache miss */
127	PM_UNALIGNED,		/* Unaligned Load/Store */
128	PM_NUM_COUNTERS
129};
130
131static inline void
132op_add_pm(unsigned long pc, int kern, unsigned long counter,
133	  struct op_counter_config *ctr, unsigned long event)
134{
135	unsigned long fake_counter = 2 + event;
136	if (counter == 1)
137		fake_counter += PM_NUM_COUNTERS;
138	if (ctr[fake_counter].enabled)
139		oprofile_add_pc(pc, kern, fake_counter);
140}
141
142static void
143ev67_handle_interrupt(unsigned long which, struct pt_regs *regs,
144		      struct op_counter_config *ctr)
145{
146	unsigned long pmpc, pctr_ctl;
147	int kern = !user_mode(regs);
148	int mispredict = 0;
149	union {
150		unsigned long v;
151		struct {
152			unsigned reserved:	30; /*  0-29 */
153			unsigned overcount:	 3; /* 30-32 */
154			unsigned icache_miss:	 1; /*    33 */
155			unsigned trap_type:	 4; /* 34-37 */
156			unsigned load_store:	 1; /*    38 */
157			unsigned trap:		 1; /*    39 */
158			unsigned mispredict:	 1; /*    40 */
159		} fields;
160	} i_stat;
161
162	enum trap_types {
163		TRAP_REPLAY,
164		TRAP_INVALID0,
165		TRAP_DTB_DOUBLE_MISS_3,
166		TRAP_DTB_DOUBLE_MISS_4,
167		TRAP_FP_DISABLED,
168		TRAP_UNALIGNED,
169		TRAP_DTB_SINGLE_MISS,
170		TRAP_DSTREAM_FAULT,
171		TRAP_OPCDEC,
172		TRAP_INVALID1,
173		TRAP_MACHINE_CHECK,
174		TRAP_INVALID2,
175		TRAP_ARITHMETIC,
176		TRAP_INVALID3,
177		TRAP_MT_FPCR,
178		TRAP_RESET
179	};
180
181	pmpc = wrperfmon(9, 0);
182	/* ??? Don't know how to handle physical-mode PALcode address.  */
183	if (pmpc & 1)
184		return;
185	pmpc &= ~2;		/* clear reserved bit */
186
187	i_stat.v = wrperfmon(8, 0);
188	if (i_stat.fields.trap) {
189		switch (i_stat.fields.trap_type) {
190		case TRAP_INVALID1:
191		case TRAP_INVALID2:
192		case TRAP_INVALID3:
193			/* Pipeline redirection occurred. PMPC points
194			   to PALcode. Recognize ITB miss by PALcode
195			   offset address, and get actual PC from
196			   EXC_ADDR.  */
197			oprofile_add_pc(regs->pc, kern, which);
198			if ((pmpc & ((1 << 15) - 1)) ==  581)
199				op_add_pm(regs->pc, kern, which,
200					  ctr, PM_ITB_MISS);
201			/* Most other bit and counter values will be
202			   those for the first instruction in the
203			   fault handler, so we're done.  */
204			return;
205		case TRAP_REPLAY:
206			op_add_pm(pmpc, kern, which, ctr,
207				  (i_stat.fields.load_store
208				   ? PM_LOAD_STORE : PM_REPLAY));
209			break;
210		case TRAP_DTB_DOUBLE_MISS_3:
211		case TRAP_DTB_DOUBLE_MISS_4:
212		case TRAP_DTB_SINGLE_MISS:
213			op_add_pm(pmpc, kern, which, ctr, PM_DTB_MISS);
214			break;
215		case TRAP_UNALIGNED:
216			op_add_pm(pmpc, kern, which, ctr, PM_UNALIGNED);
217			break;
218		case TRAP_INVALID0:
219		case TRAP_FP_DISABLED:
220		case TRAP_DSTREAM_FAULT:
221		case TRAP_OPCDEC:
222		case TRAP_MACHINE_CHECK:
223		case TRAP_ARITHMETIC:
224		case TRAP_MT_FPCR:
225		case TRAP_RESET:
226			break;
227		}
228
229		/* ??? JSR/JMP/RET/COR or HW_JSR/HW_JMP/HW_RET/HW_COR
230		   mispredicts do not set this bit but can be
231		   recognized by the presence of one of these
232		   instructions at the PMPC location with bit 39
233		   set.  */
234		if (i_stat.fields.mispredict) {
235			mispredict = 1;
236			op_add_pm(pmpc, kern, which, ctr, PM_MISPREDICT);
237		}
238	}
239
240	oprofile_add_pc(pmpc, kern, which);
241
242	pctr_ctl = wrperfmon(5, 0);
243	if (pctr_ctl & (1UL << 27))
244		op_add_pm(pmpc, kern, which, ctr, PM_STALLED);
245
246	/* Unfortunately, TAK is undefined on mispredicted branches.
247	   ??? It is also undefined for non-cbranch insns, should
248	   check that.  */
249	if (!mispredict && pctr_ctl & (1UL << 0))
250		op_add_pm(pmpc, kern, which, ctr, PM_TAKEN);
251}
252
253struct op_axp_model op_model_ev67 = {
254	.reg_setup		= ev67_reg_setup,
255	.cpu_setup		= ev67_cpu_setup,
256	.reset_ctr		= ev67_reset_ctr,
257	.handle_interrupt	= ev67_handle_interrupt,
258	.cpu_type		= "alpha/ev67",
259	.num_counters		= 20,
260	.can_set_proc_mode	= 0,
261};
262