| /** | 
 |  * @file arch/alpha/oprofile/op_model_ev67.c | 
 |  * | 
 |  * @remark Copyright 2002 OProfile authors | 
 |  * @remark Read the file COPYING | 
 |  * | 
 |  * @author Richard Henderson <rth@twiddle.net> | 
 |  * @author Falk Hueffner <falk@debian.org> | 
 |  */ | 
 |  | 
 | #include <linux/oprofile.h> | 
 | #include <linux/smp.h> | 
 | #include <asm/ptrace.h> | 
 |  | 
 | #include "op_impl.h" | 
 |  | 
 |  | 
 | /* Compute all of the registers in preparation for enabling profiling.  */ | 
 |  | 
 | static void | 
 | ev67_reg_setup(struct op_register_config *reg, | 
 | 	       struct op_counter_config *ctr, | 
 | 	       struct op_system_config *sys) | 
 | { | 
 | 	unsigned long ctl, reset, need_reset, i; | 
 |  | 
 | 	/* Select desired events.  */ | 
 | 	ctl = 1UL << 4;		/* Enable ProfileMe mode. */ | 
 |  | 
 | 	/* The event numbers are chosen so we can use them directly if | 
 | 	   PCTR1 is enabled.  */ | 
 | 	if (ctr[1].enabled) { | 
 | 		ctl |= (ctr[1].event & 3) << 2; | 
 | 	} else { | 
 | 		if (ctr[0].event == 0) /* cycles */ | 
 | 			ctl |= 1UL << 2; | 
 | 	} | 
 | 	reg->mux_select = ctl; | 
 |  | 
 | 	/* Select logging options.  */ | 
 | 	/* ??? Need to come up with some mechanism to trace only | 
 | 	   selected processes.  EV67 does not have a mechanism to | 
 | 	   select kernel or user mode only.  For now, enable always.  */ | 
 | 	reg->proc_mode = 0; | 
 |  | 
 | 	/* EV67 cannot change the width of the counters as with the | 
 | 	   other implementations.  But fortunately, we can write to | 
 | 	   the counters and set the value such that it will overflow | 
 | 	   at the right time.  */ | 
 | 	reset = need_reset = 0; | 
 | 	for (i = 0; i < 2; ++i) { | 
 | 		unsigned long count = ctr[i].count; | 
 | 		if (!ctr[i].enabled) | 
 | 			continue; | 
 |  | 
 | 		if (count > 0x100000) | 
 | 			count = 0x100000; | 
 | 		ctr[i].count = count; | 
 | 		reset |= (0x100000 - count) << (i ? 6 : 28); | 
 | 		if (count != 0x100000) | 
 | 			need_reset |= 1 << i; | 
 | 	} | 
 | 	reg->reset_values = reset; | 
 | 	reg->need_reset = need_reset; | 
 | } | 
 |  | 
 | /* Program all of the registers in preparation for enabling profiling.  */ | 
 |  | 
 | static void | 
 | ev67_cpu_setup (void *x) | 
 | { | 
 | 	struct op_register_config *reg = x; | 
 |  | 
 | 	wrperfmon(2, reg->mux_select); | 
 | 	wrperfmon(3, reg->proc_mode); | 
 | 	wrperfmon(6, reg->reset_values | 3); | 
 | } | 
 |  | 
 | /* CTR is a counter for which the user has requested an interrupt count | 
 |    in between one of the widths selectable in hardware.  Reset the count | 
 |    for CTR to the value stored in REG->RESET_VALUES.  */ | 
 |  | 
 | static void | 
 | ev67_reset_ctr(struct op_register_config *reg, unsigned long ctr) | 
 | { | 
 | 	wrperfmon(6, reg->reset_values | (1 << ctr)); | 
 | } | 
 |  | 
 | /* ProfileMe conditions which will show up as counters. We can also | 
 |    detect the following, but it seems unlikely that anybody is | 
 |    interested in counting them: | 
 |     * Reset | 
 |     * MT_FPCR (write to floating point control register) | 
 |     * Arithmetic trap | 
 |     * Dstream Fault | 
 |     * Machine Check (ECC fault, etc.) | 
 |     * OPCDEC (illegal opcode) | 
 |     * Floating point disabled | 
 |     * Differentiate between DTB single/double misses and 3 or 4 level | 
 |       page tables | 
 |     * Istream access violation | 
 |     * Interrupt | 
 |     * Icache Parity Error. | 
 |     * Instruction killed (nop, trapb) | 
 |  | 
 |    Unfortunately, there seems to be no way to detect Dcache and Bcache | 
 |    misses; the latter could be approximated by making the counter | 
 |    count Bcache misses, but that is not precise. | 
 |  | 
 |    We model this as 20 counters: | 
 |     * PCTR0 | 
 |     * PCTR1 | 
 |     * 9 ProfileMe events, induced by PCTR0 | 
 |     * 9 ProfileMe events, induced by PCTR1 | 
 | */ | 
 |  | 
 | enum profileme_counters { | 
 | 	PM_STALLED,		/* Stalled for at least one cycle | 
 | 				   between the fetch and map stages  */ | 
 | 	PM_TAKEN,		/* Conditional branch taken */ | 
 | 	PM_MISPREDICT,		/* Branch caused mispredict trap */ | 
 | 	PM_ITB_MISS,		/* ITB miss */ | 
 | 	PM_DTB_MISS,		/* DTB miss */ | 
 | 	PM_REPLAY,		/* Replay trap */ | 
 | 	PM_LOAD_STORE,		/* Load-store order trap */ | 
 | 	PM_ICACHE_MISS,		/* Icache miss */ | 
 | 	PM_UNALIGNED,		/* Unaligned Load/Store */ | 
 | 	PM_NUM_COUNTERS | 
 | }; | 
 |  | 
 | static inline void | 
 | op_add_pm(unsigned long pc, int kern, unsigned long counter, | 
 | 	  struct op_counter_config *ctr, unsigned long event) | 
 | { | 
 | 	unsigned long fake_counter = 2 + event; | 
 | 	if (counter == 1) | 
 | 		fake_counter += PM_NUM_COUNTERS; | 
 | 	if (ctr[fake_counter].enabled) | 
 | 		oprofile_add_pc(pc, kern, fake_counter); | 
 | } | 
 |  | 
 | static void | 
 | ev67_handle_interrupt(unsigned long which, struct pt_regs *regs, | 
 | 		      struct op_counter_config *ctr) | 
 | { | 
 | 	unsigned long pmpc, pctr_ctl; | 
 | 	int kern = !user_mode(regs); | 
 | 	int mispredict = 0; | 
 | 	union { | 
 | 		unsigned long v; | 
 | 		struct { | 
 | 			unsigned reserved:	30; /*  0-29 */ | 
 | 			unsigned overcount:	 3; /* 30-32 */ | 
 | 			unsigned icache_miss:	 1; /*    33 */ | 
 | 			unsigned trap_type:	 4; /* 34-37 */ | 
 | 			unsigned load_store:	 1; /*    38 */ | 
 | 			unsigned trap:		 1; /*    39 */ | 
 | 			unsigned mispredict:	 1; /*    40 */ | 
 | 		} fields; | 
 | 	} i_stat; | 
 |  | 
 | 	enum trap_types { | 
 | 		TRAP_REPLAY, | 
 | 		TRAP_INVALID0, | 
 | 		TRAP_DTB_DOUBLE_MISS_3, | 
 | 		TRAP_DTB_DOUBLE_MISS_4, | 
 | 		TRAP_FP_DISABLED, | 
 | 		TRAP_UNALIGNED, | 
 | 		TRAP_DTB_SINGLE_MISS, | 
 | 		TRAP_DSTREAM_FAULT, | 
 | 		TRAP_OPCDEC, | 
 | 		TRAP_INVALID1, | 
 | 		TRAP_MACHINE_CHECK, | 
 | 		TRAP_INVALID2, | 
 | 		TRAP_ARITHMETIC, | 
 | 		TRAP_INVALID3, | 
 | 		TRAP_MT_FPCR, | 
 | 		TRAP_RESET | 
 | 	}; | 
 |  | 
 | 	pmpc = wrperfmon(9, 0); | 
 | 	/* ??? Don't know how to handle physical-mode PALcode address.  */ | 
 | 	if (pmpc & 1) | 
 | 		return; | 
 | 	pmpc &= ~2;		/* clear reserved bit */ | 
 |  | 
 | 	i_stat.v = wrperfmon(8, 0); | 
 | 	if (i_stat.fields.trap) { | 
 | 		switch (i_stat.fields.trap_type) { | 
 | 		case TRAP_INVALID1: | 
 | 		case TRAP_INVALID2: | 
 | 		case TRAP_INVALID3: | 
 | 			/* Pipeline redirection occurred. PMPC points | 
 | 			   to PALcode. Recognize ITB miss by PALcode | 
 | 			   offset address, and get actual PC from | 
 | 			   EXC_ADDR.  */ | 
 | 			oprofile_add_pc(regs->pc, kern, which); | 
 | 			if ((pmpc & ((1 << 15) - 1)) ==  581) | 
 | 				op_add_pm(regs->pc, kern, which, | 
 | 					  ctr, PM_ITB_MISS); | 
 | 			/* Most other bit and counter values will be | 
 | 			   those for the first instruction in the | 
 | 			   fault handler, so we're done.  */ | 
 | 			return; | 
 | 		case TRAP_REPLAY: | 
 | 			op_add_pm(pmpc, kern, which, ctr, | 
 | 				  (i_stat.fields.load_store | 
 | 				   ? PM_LOAD_STORE : PM_REPLAY)); | 
 | 			break; | 
 | 		case TRAP_DTB_DOUBLE_MISS_3: | 
 | 		case TRAP_DTB_DOUBLE_MISS_4: | 
 | 		case TRAP_DTB_SINGLE_MISS: | 
 | 			op_add_pm(pmpc, kern, which, ctr, PM_DTB_MISS); | 
 | 			break; | 
 | 		case TRAP_UNALIGNED: | 
 | 			op_add_pm(pmpc, kern, which, ctr, PM_UNALIGNED); | 
 | 			break; | 
 | 		case TRAP_INVALID0: | 
 | 		case TRAP_FP_DISABLED: | 
 | 		case TRAP_DSTREAM_FAULT: | 
 | 		case TRAP_OPCDEC: | 
 | 		case TRAP_MACHINE_CHECK: | 
 | 		case TRAP_ARITHMETIC: | 
 | 		case TRAP_MT_FPCR: | 
 | 		case TRAP_RESET: | 
 | 			break; | 
 | 		} | 
 |  | 
 | 		/* ??? JSR/JMP/RET/COR or HW_JSR/HW_JMP/HW_RET/HW_COR | 
 | 		   mispredicts do not set this bit but can be | 
 | 		   recognized by the presence of one of these | 
 | 		   instructions at the PMPC location with bit 39 | 
 | 		   set.  */ | 
 | 		if (i_stat.fields.mispredict) { | 
 | 			mispredict = 1; | 
 | 			op_add_pm(pmpc, kern, which, ctr, PM_MISPREDICT); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	oprofile_add_pc(pmpc, kern, which); | 
 |  | 
 | 	pctr_ctl = wrperfmon(5, 0); | 
 | 	if (pctr_ctl & (1UL << 27)) | 
 | 		op_add_pm(pmpc, kern, which, ctr, PM_STALLED); | 
 |  | 
 | 	/* Unfortunately, TAK is undefined on mispredicted branches. | 
 | 	   ??? It is also undefined for non-cbranch insns, should | 
 | 	   check that.  */ | 
 | 	if (!mispredict && pctr_ctl & (1UL << 0)) | 
 | 		op_add_pm(pmpc, kern, which, ctr, PM_TAKEN); | 
 | } | 
 |  | 
 | struct op_axp_model op_model_ev67 = { | 
 | 	.reg_setup		= ev67_reg_setup, | 
 | 	.cpu_setup		= ev67_cpu_setup, | 
 | 	.reset_ctr		= ev67_reset_ctr, | 
 | 	.handle_interrupt	= ev67_handle_interrupt, | 
 | 	.cpu_type		= "alpha/ev67", | 
 | 	.num_counters		= 20, | 
 | 	.can_set_proc_mode	= 0, | 
 | }; |