133 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			3.0 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /**
 | |
|  * @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();
 | |
| }
 | 
