diff options
Diffstat (limited to 'arch/mips/kernel/perf_event.c')
| -rw-r--r-- | arch/mips/kernel/perf_event.c | 542 | 
1 files changed, 5 insertions, 537 deletions
diff --git a/arch/mips/kernel/perf_event.c b/arch/mips/kernel/perf_event.c index 2b7f3f703b8..c1cf9c6c3f7 100644 --- a/arch/mips/kernel/perf_event.c +++ b/arch/mips/kernel/perf_event.c @@ -14,505 +14,16 @@   * published by the Free Software Foundation.   */ -#include <linux/cpumask.h> -#include <linux/interrupt.h> -#include <linux/smp.h> -#include <linux/kernel.h>  #include <linux/perf_event.h> -#include <linux/uaccess.h> -#include <asm/irq.h> -#include <asm/irq_regs.h>  #include <asm/stacktrace.h> -#include <asm/time.h> /* For perf_irq */ - -/* These are for 32bit counters. For 64bit ones, define them accordingly. */ -#define MAX_PERIOD	((1ULL << 32) - 1) -#define VALID_COUNT	0x7fffffff -#define TOTAL_BITS	32 -#define HIGHEST_BIT	31 - -#define MIPS_MAX_HWEVENTS 4 - -struct cpu_hw_events { -	/* Array of events on this cpu. */ -	struct perf_event	*events[MIPS_MAX_HWEVENTS]; - -	/* -	 * Set the bit (indexed by the counter number) when the counter -	 * is used for an event. -	 */ -	unsigned long		used_mask[BITS_TO_LONGS(MIPS_MAX_HWEVENTS)]; - -	/* -	 * The borrowed MSB for the performance counter. A MIPS performance -	 * counter uses its bit 31 (for 32bit counters) or bit 63 (for 64bit -	 * counters) as a factor of determining whether a counter overflow -	 * should be signaled. So here we use a separate MSB for each -	 * counter to make things easy. -	 */ -	unsigned long		msbs[BITS_TO_LONGS(MIPS_MAX_HWEVENTS)]; - -	/* -	 * Software copy of the control register for each performance counter. -	 * MIPS CPUs vary in performance counters. They use this differently, -	 * and even may not use it. -	 */ -	unsigned int		saved_ctrl[MIPS_MAX_HWEVENTS]; -}; -DEFINE_PER_CPU(struct cpu_hw_events, cpu_hw_events) = { -	.saved_ctrl = {0}, -}; - -/* The description of MIPS performance events. */ -struct mips_perf_event { -	unsigned int event_id; -	/* -	 * MIPS performance counters are indexed starting from 0. -	 * CNTR_EVEN indicates the indexes of the counters to be used are -	 * even numbers. -	 */ -	unsigned int cntr_mask; -	#define CNTR_EVEN	0x55555555 -	#define CNTR_ODD	0xaaaaaaaa -#ifdef CONFIG_MIPS_MT_SMP -	enum { -		T  = 0, -		V  = 1, -		P  = 2, -	} range; -#else -	#define T -	#define V -	#define P -#endif -}; - -static struct mips_perf_event raw_event; -static DEFINE_MUTEX(raw_event_mutex); - -#define UNSUPPORTED_PERF_EVENT_ID 0xffffffff -#define C(x) PERF_COUNT_HW_CACHE_##x - -struct mips_pmu { -	const char	*name; -	int		irq; -	irqreturn_t	(*handle_irq)(int irq, void *dev); -	int		(*handle_shared_irq)(void); -	void		(*start)(void); -	void		(*stop)(void); -	int		(*alloc_counter)(struct cpu_hw_events *cpuc, -					struct hw_perf_event *hwc); -	u64		(*read_counter)(unsigned int idx); -	void		(*write_counter)(unsigned int idx, u64 val); -	void		(*enable_event)(struct hw_perf_event *evt, int idx); -	void		(*disable_event)(int idx); -	const struct mips_perf_event *(*map_raw_event)(u64 config); -	const struct mips_perf_event (*general_event_map)[PERF_COUNT_HW_MAX]; -	const struct mips_perf_event (*cache_event_map) -				[PERF_COUNT_HW_CACHE_MAX] -				[PERF_COUNT_HW_CACHE_OP_MAX] -				[PERF_COUNT_HW_CACHE_RESULT_MAX]; -	unsigned int	num_counters; -}; - -static const struct mips_pmu *mipspmu; - -static int -mipspmu_event_set_period(struct perf_event *event, -			struct hw_perf_event *hwc, -			int idx) -{ -	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events); -	s64 left = local64_read(&hwc->period_left); -	s64 period = hwc->sample_period; -	int ret = 0; -	u64 uleft; -	unsigned long flags; - -	if (unlikely(left <= -period)) { -		left = period; -		local64_set(&hwc->period_left, left); -		hwc->last_period = period; -		ret = 1; -	} - -	if (unlikely(left <= 0)) { -		left += period; -		local64_set(&hwc->period_left, left); -		hwc->last_period = period; -		ret = 1; -	} - -	if (left > (s64)MAX_PERIOD) -		left = MAX_PERIOD; - -	local64_set(&hwc->prev_count, (u64)-left); - -	local_irq_save(flags); -	uleft = (u64)(-left) & MAX_PERIOD; -	uleft > VALID_COUNT ? -		set_bit(idx, cpuc->msbs) : clear_bit(idx, cpuc->msbs); -	mipspmu->write_counter(idx, (u64)(-left) & VALID_COUNT); -	local_irq_restore(flags); - -	perf_event_update_userpage(event); - -	return ret; -} - -static int mipspmu_enable(struct perf_event *event) -{ -	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events); -	struct hw_perf_event *hwc = &event->hw; -	int idx; -	int err = 0; - -	/* To look for a free counter for this event. */ -	idx = mipspmu->alloc_counter(cpuc, hwc); -	if (idx < 0) { -		err = idx; -		goto out; -	} - -	/* -	 * If there is an event in the counter we are going to use then -	 * make sure it is disabled. -	 */ -	event->hw.idx = idx; -	mipspmu->disable_event(idx); -	cpuc->events[idx] = event; - -	/* Set the period for the event. */ -	mipspmu_event_set_period(event, hwc, idx); - -	/* Enable the event. */ -	mipspmu->enable_event(hwc, idx); - -	/* Propagate our changes to the userspace mapping. */ -	perf_event_update_userpage(event); - -out: -	return err; -} - -static void mipspmu_event_update(struct perf_event *event, -			struct hw_perf_event *hwc, -			int idx) -{ -	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events); -	unsigned long flags; -	int shift = 64 - TOTAL_BITS; -	s64 prev_raw_count, new_raw_count; -	s64 delta; - -again: -	prev_raw_count = local64_read(&hwc->prev_count); -	local_irq_save(flags); -	/* Make the counter value be a "real" one. */ -	new_raw_count = mipspmu->read_counter(idx); -	if (new_raw_count & (test_bit(idx, cpuc->msbs) << HIGHEST_BIT)) { -		new_raw_count &= VALID_COUNT; -		clear_bit(idx, cpuc->msbs); -	} else -		new_raw_count |= (test_bit(idx, cpuc->msbs) << HIGHEST_BIT); -	local_irq_restore(flags); - -	if (local64_cmpxchg(&hwc->prev_count, prev_raw_count, -				new_raw_count) != prev_raw_count) -		goto again; - -	delta = (new_raw_count << shift) - (prev_raw_count << shift); -	delta >>= shift; - -	local64_add(delta, &event->count); -	local64_sub(delta, &hwc->period_left); - -	return; -} - -static void mipspmu_disable(struct perf_event *event) -{ -	struct cpu_hw_events *cpuc = &__get_cpu_var(cpu_hw_events); -	struct hw_perf_event *hwc = &event->hw; -	int idx = hwc->idx; - - -	WARN_ON(idx < 0 || idx >= mipspmu->num_counters); - -	/* We are working on a local event. */ -	mipspmu->disable_event(idx); - -	barrier(); - -	mipspmu_event_update(event, hwc, idx); -	cpuc->events[idx] = NULL; -	clear_bit(idx, cpuc->used_mask); - -	perf_event_update_userpage(event); -} - -static void mipspmu_unthrottle(struct perf_event *event) -{ -	struct hw_perf_event *hwc = &event->hw; - -	mipspmu->enable_event(hwc, hwc->idx); -} - -static void mipspmu_read(struct perf_event *event) -{ -	struct hw_perf_event *hwc = &event->hw; - -	/* Don't read disabled counters! */ -	if (hwc->idx < 0) -		return; - -	mipspmu_event_update(event, hwc, hwc->idx); -} - -static struct pmu pmu = { -	.enable		= mipspmu_enable, -	.disable	= mipspmu_disable, -	.unthrottle	= mipspmu_unthrottle, -	.read		= mipspmu_read, -}; - -static atomic_t active_events = ATOMIC_INIT(0); -static DEFINE_MUTEX(pmu_reserve_mutex); -static int (*save_perf_irq)(void); - -static int mipspmu_get_irq(void) -{ -	int err; - -	if (mipspmu->irq >= 0) { -		/* Request my own irq handler. */ -		err = request_irq(mipspmu->irq, mipspmu->handle_irq, -			IRQF_DISABLED | IRQF_NOBALANCING, -			"mips_perf_pmu", NULL); -		if (err) { -			pr_warning("Unable to request IRQ%d for MIPS " -			   "performance counters!\n", mipspmu->irq); -		} -	} else if (cp0_perfcount_irq < 0) { -		/* -		 * We are sharing the irq number with the timer interrupt. -		 */ -		save_perf_irq = perf_irq; -		perf_irq = mipspmu->handle_shared_irq; -		err = 0; -	} else { -		pr_warning("The platform hasn't properly defined its " -			"interrupt controller.\n"); -		err = -ENOENT; -	} - -	return err; -} - -static void mipspmu_free_irq(void) -{ -	if (mipspmu->irq >= 0) -		free_irq(mipspmu->irq, NULL); -	else if (cp0_perfcount_irq < 0) -		perf_irq = save_perf_irq; -} - -static inline unsigned int -mipspmu_perf_event_encode(const struct mips_perf_event *pev) -{ -/* - * Top 8 bits for range, next 16 bits for cntr_mask, lowest 8 bits for - * event_id. - */ -#ifdef CONFIG_MIPS_MT_SMP -	return ((unsigned int)pev->range << 24) | -		(pev->cntr_mask & 0xffff00) | -		(pev->event_id & 0xff); -#else -	return (pev->cntr_mask & 0xffff00) | -		(pev->event_id & 0xff); -#endif -} - -static const struct mips_perf_event * -mipspmu_map_general_event(int idx) -{ -	const struct mips_perf_event *pev; - -	pev = ((*mipspmu->general_event_map)[idx].event_id == -		UNSUPPORTED_PERF_EVENT_ID ? ERR_PTR(-EOPNOTSUPP) : -		&(*mipspmu->general_event_map)[idx]); - -	return pev; -} - -static const struct mips_perf_event * -mipspmu_map_cache_event(u64 config) -{ -	unsigned int cache_type, cache_op, cache_result; -	const struct mips_perf_event *pev; - -	cache_type = (config >> 0) & 0xff; -	if (cache_type >= PERF_COUNT_HW_CACHE_MAX) -		return ERR_PTR(-EINVAL); - -	cache_op = (config >> 8) & 0xff; -	if (cache_op >= PERF_COUNT_HW_CACHE_OP_MAX) -		return ERR_PTR(-EINVAL); - -	cache_result = (config >> 16) & 0xff; -	if (cache_result >= PERF_COUNT_HW_CACHE_RESULT_MAX) -		return ERR_PTR(-EINVAL); - -	pev = &((*mipspmu->cache_event_map) -					[cache_type] -					[cache_op] -					[cache_result]); - -	if (pev->event_id == UNSUPPORTED_PERF_EVENT_ID) -		return ERR_PTR(-EOPNOTSUPP); - -	return pev; - -} - -static int validate_event(struct cpu_hw_events *cpuc, -	       struct perf_event *event) -{ -	struct hw_perf_event fake_hwc = event->hw; - -	if (event->pmu && event->pmu != &pmu) -		return 0; - -	return mipspmu->alloc_counter(cpuc, &fake_hwc) >= 0; -} - -static int validate_group(struct perf_event *event) -{ -	struct perf_event *sibling, *leader = event->group_leader; -	struct cpu_hw_events fake_cpuc; - -	memset(&fake_cpuc, 0, sizeof(fake_cpuc)); - -	if (!validate_event(&fake_cpuc, leader)) -		return -ENOSPC; - -	list_for_each_entry(sibling, &leader->sibling_list, group_entry) { -		if (!validate_event(&fake_cpuc, sibling)) -			return -ENOSPC; -	} - -	if (!validate_event(&fake_cpuc, event)) -		return -ENOSPC; - -	return 0; -} - -/* - * mipsxx/rm9000/loongson2 have different performance counters, they have - * specific low-level init routines. - */ -static void reset_counters(void *arg); -static int __hw_perf_event_init(struct perf_event *event); - -static void hw_perf_event_destroy(struct perf_event *event) -{ -	if (atomic_dec_and_mutex_lock(&active_events, -				&pmu_reserve_mutex)) { -		/* -		 * We must not call the destroy function with interrupts -		 * disabled. -		 */ -		on_each_cpu(reset_counters, -			(void *)(long)mipspmu->num_counters, 1); -		mipspmu_free_irq(); -		mutex_unlock(&pmu_reserve_mutex); -	} -} - -const struct pmu *hw_perf_event_init(struct perf_event *event) -{ -	int err = 0; - -	if (!mipspmu || event->cpu >= nr_cpumask_bits || -		(event->cpu >= 0 && !cpu_online(event->cpu))) -		return ERR_PTR(-ENODEV); - -	if (!atomic_inc_not_zero(&active_events)) { -		if (atomic_read(&active_events) > MIPS_MAX_HWEVENTS) { -			atomic_dec(&active_events); -			return ERR_PTR(-ENOSPC); -		} - -		mutex_lock(&pmu_reserve_mutex); -		if (atomic_read(&active_events) == 0) -			err = mipspmu_get_irq(); - -		if (!err) -			atomic_inc(&active_events); -		mutex_unlock(&pmu_reserve_mutex); -	} - -	if (err) -		return ERR_PTR(err); - -	err = __hw_perf_event_init(event); -	if (err) -		hw_perf_event_destroy(event); - -	return err ? ERR_PTR(err) : &pmu; -} - -void hw_perf_enable(void) -{ -	if (mipspmu) -		mipspmu->start(); -} - -void hw_perf_disable(void) -{ -	if (mipspmu) -		mipspmu->stop(); -} - -/* This is needed by specific irq handlers in perf_event_*.c */ -static void -handle_associated_event(struct cpu_hw_events *cpuc, -	int idx, struct perf_sample_data *data, struct pt_regs *regs) -{ -	struct perf_event *event = cpuc->events[idx]; -	struct hw_perf_event *hwc = &event->hw; - -	mipspmu_event_update(event, hwc, idx); -	data->period = event->hw.last_period; -	if (!mipspmu_event_set_period(event, hwc, idx)) -		return; - -	if (perf_event_overflow(event, 0, data, regs)) -		mipspmu->disable_event(idx); -} - -#include "perf_event_mipsxx.c"  /* Callchain handling code. */ -static inline void -callchain_store(struct perf_callchain_entry *entry, -		u64 ip) -{ -	if (entry->nr < PERF_MAX_STACK_DEPTH) -		entry->ip[entry->nr++] = ip; -}  /*   * Leave userspace callchain empty for now. When we find a way to trace - * the user stack callchains, we add here. + * the user stack callchains, we will add it here.   */ -static void -perf_callchain_user(struct pt_regs *regs, -		    struct perf_callchain_entry *entry) -{ -}  static void save_raw_perf_callchain(struct perf_callchain_entry *entry,  	unsigned long reg29) @@ -523,23 +34,21 @@ static void save_raw_perf_callchain(struct perf_callchain_entry *entry,  	while (!kstack_end(sp)) {  		addr = *sp++;  		if (__kernel_text_address(addr)) { -			callchain_store(entry, addr); +			perf_callchain_store(entry, addr);  			if (entry->nr >= PERF_MAX_STACK_DEPTH)  				break;  		}  	}  } -static void -perf_callchain_kernel(struct pt_regs *regs, -		      struct perf_callchain_entry *entry) +void perf_callchain_kernel(struct perf_callchain_entry *entry, +		      struct pt_regs *regs)  {  	unsigned long sp = regs->regs[29];  #ifdef CONFIG_KALLSYMS  	unsigned long ra = regs->regs[31];  	unsigned long pc = regs->cp0_epc; -	callchain_store(entry, PERF_CONTEXT_KERNEL);  	if (raw_show_trace || !__kernel_text_address(pc)) {  		unsigned long stack_page =  			(unsigned long)task_stack_page(current); @@ -549,53 +58,12 @@ perf_callchain_kernel(struct pt_regs *regs,  		return;  	}  	do { -		callchain_store(entry, pc); +		perf_callchain_store(entry, pc);  		if (entry->nr >= PERF_MAX_STACK_DEPTH)  			break;  		pc = unwind_stack(current, &sp, pc, &ra);  	} while (pc);  #else -	callchain_store(entry, PERF_CONTEXT_KERNEL);  	save_raw_perf_callchain(entry, sp);  #endif  } - -static void -perf_do_callchain(struct pt_regs *regs, -		  struct perf_callchain_entry *entry) -{ -	int is_user; - -	if (!regs) -		return; - -	is_user = user_mode(regs); - -	if (!current || !current->pid) -		return; - -	if (is_user && current->state != TASK_RUNNING) -		return; - -	if (!is_user) { -		perf_callchain_kernel(regs, entry); -		if (current->mm) -			regs = task_pt_regs(current); -		else -			regs = NULL; -	} -	if (regs) -		perf_callchain_user(regs, entry); -} - -static DEFINE_PER_CPU(struct perf_callchain_entry, pmc_irq_entry); - -struct perf_callchain_entry * -perf_callchain(struct pt_regs *regs) -{ -	struct perf_callchain_entry *entry = &__get_cpu_var(pmc_irq_entry); - -	entry->nr = 0; -	perf_do_callchain(regs, entry); -	return entry; -}  | 
