Add Oprofile kernel support for ARMv7. Tested on OMAP3430 and OMAP3530 chipsets (Cortex-A8). Signed-off-by: Jean Pihet <jpihet@mvista.com> Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
		
			
				
	
	
		
			411 lines
		
	
	
	
		
			8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			411 lines
		
	
	
	
		
			8 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
/**
 | 
						|
 * op_model_v7.c
 | 
						|
 * ARM V7 (Cortex A8) Event Monitor Driver
 | 
						|
 *
 | 
						|
 * Copyright 2008 Jean Pihet <jpihet@mvista.com>
 | 
						|
 * Copyright 2004 ARM SMP Development Team
 | 
						|
 *
 | 
						|
 * This program is free software; you can redistribute it and/or modify
 | 
						|
 * it under the terms of the GNU General Public License version 2 as
 | 
						|
 * published by the Free Software Foundation.
 | 
						|
 */
 | 
						|
#include <linux/types.h>
 | 
						|
#include <linux/errno.h>
 | 
						|
#include <linux/oprofile.h>
 | 
						|
#include <linux/interrupt.h>
 | 
						|
#include <linux/irq.h>
 | 
						|
#include <linux/smp.h>
 | 
						|
 | 
						|
#include "op_counter.h"
 | 
						|
#include "op_arm_model.h"
 | 
						|
#include "op_model_v7.h"
 | 
						|
 | 
						|
/* #define DEBUG */
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * ARM V7 PMNC support
 | 
						|
 */
 | 
						|
 | 
						|
static u32 cnt_en[CNTMAX];
 | 
						|
 | 
						|
static inline void armv7_pmnc_write(u32 val)
 | 
						|
{
 | 
						|
	val &= PMNC_MASK;
 | 
						|
	asm volatile("mcr p15, 0, %0, c9, c12, 0" : : "r" (val));
 | 
						|
}
 | 
						|
 | 
						|
static inline u32 armv7_pmnc_read(void)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val));
 | 
						|
	return val;
 | 
						|
}
 | 
						|
 | 
						|
static inline u32 armv7_pmnc_enable_counter(unsigned int cnt)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
 | 
						|
	if (cnt >= CNTMAX) {
 | 
						|
		printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter"
 | 
						|
			" %d\n", smp_processor_id(), cnt);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (cnt == CCNT)
 | 
						|
		val = CNTENS_C;
 | 
						|
	else
 | 
						|
		val = (1 << (cnt - CNT0));
 | 
						|
 | 
						|
	val &= CNTENS_MASK;
 | 
						|
	asm volatile("mcr p15, 0, %0, c9, c12, 1" : : "r" (val));
 | 
						|
 | 
						|
	return cnt;
 | 
						|
}
 | 
						|
 | 
						|
static inline u32 armv7_pmnc_disable_counter(unsigned int cnt)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
 | 
						|
	if (cnt >= CNTMAX) {
 | 
						|
		printk(KERN_ERR "oprofile: CPU%u disabling wrong PMNC counter"
 | 
						|
			" %d\n", smp_processor_id(), cnt);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (cnt == CCNT)
 | 
						|
		val = CNTENC_C;
 | 
						|
	else
 | 
						|
		val = (1 << (cnt - CNT0));
 | 
						|
 | 
						|
	val &= CNTENC_MASK;
 | 
						|
	asm volatile("mcr p15, 0, %0, c9, c12, 2" : : "r" (val));
 | 
						|
 | 
						|
	return cnt;
 | 
						|
}
 | 
						|
 | 
						|
static inline u32 armv7_pmnc_enable_intens(unsigned int cnt)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
 | 
						|
	if (cnt >= CNTMAX) {
 | 
						|
		printk(KERN_ERR "oprofile: CPU%u enabling wrong PMNC counter"
 | 
						|
			" interrupt enable %d\n", smp_processor_id(), cnt);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (cnt == CCNT)
 | 
						|
		val = INTENS_C;
 | 
						|
	else
 | 
						|
		val = (1 << (cnt - CNT0));
 | 
						|
 | 
						|
	val &= INTENS_MASK;
 | 
						|
	asm volatile("mcr p15, 0, %0, c9, c14, 1" : : "r" (val));
 | 
						|
 | 
						|
	return cnt;
 | 
						|
}
 | 
						|
 | 
						|
static inline u32 armv7_pmnc_getreset_flags(void)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
 | 
						|
	/* Read */
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val));
 | 
						|
 | 
						|
	/* Write to clear flags */
 | 
						|
	val &= FLAG_MASK;
 | 
						|
	asm volatile("mcr p15, 0, %0, c9, c12, 3" : : "r" (val));
 | 
						|
 | 
						|
	return val;
 | 
						|
}
 | 
						|
 | 
						|
static inline int armv7_pmnc_select_counter(unsigned int cnt)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
 | 
						|
	if ((cnt == CCNT) || (cnt >= CNTMAX)) {
 | 
						|
		printk(KERN_ERR "oprofile: CPU%u selecting wrong PMNC counteri"
 | 
						|
			" %d\n", smp_processor_id(), cnt);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	val = (cnt - CNT0) & SELECT_MASK;
 | 
						|
	asm volatile("mcr p15, 0, %0, c9, c12, 5" : : "r" (val));
 | 
						|
 | 
						|
	return cnt;
 | 
						|
}
 | 
						|
 | 
						|
static inline void armv7_pmnc_write_evtsel(unsigned int cnt, u32 val)
 | 
						|
{
 | 
						|
	if (armv7_pmnc_select_counter(cnt) == cnt) {
 | 
						|
		val &= EVTSEL_MASK;
 | 
						|
		asm volatile("mcr p15, 0, %0, c9, c13, 1" : : "r" (val));
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void armv7_pmnc_reset_counter(unsigned int cnt)
 | 
						|
{
 | 
						|
	u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt);
 | 
						|
	u32 val = -(u32)counter_config[cpu_cnt].count;
 | 
						|
 | 
						|
	switch (cnt) {
 | 
						|
	case CCNT:
 | 
						|
		armv7_pmnc_disable_counter(cnt);
 | 
						|
 | 
						|
		asm volatile("mcr p15, 0, %0, c9, c13, 0" : : "r" (val));
 | 
						|
 | 
						|
		if (cnt_en[cnt] != 0)
 | 
						|
		    armv7_pmnc_enable_counter(cnt);
 | 
						|
 | 
						|
		break;
 | 
						|
 | 
						|
	case CNT0:
 | 
						|
	case CNT1:
 | 
						|
	case CNT2:
 | 
						|
	case CNT3:
 | 
						|
		armv7_pmnc_disable_counter(cnt);
 | 
						|
 | 
						|
		if (armv7_pmnc_select_counter(cnt) == cnt)
 | 
						|
		    asm volatile("mcr p15, 0, %0, c9, c13, 2" : : "r" (val));
 | 
						|
 | 
						|
		if (cnt_en[cnt] != 0)
 | 
						|
		    armv7_pmnc_enable_counter(cnt);
 | 
						|
 | 
						|
		break;
 | 
						|
 | 
						|
	default:
 | 
						|
		printk(KERN_ERR "oprofile: CPU%u resetting wrong PMNC counter"
 | 
						|
			" %d\n", smp_processor_id(), cnt);
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
int armv7_setup_pmnc(void)
 | 
						|
{
 | 
						|
	unsigned int cnt;
 | 
						|
 | 
						|
	if (armv7_pmnc_read() & PMNC_E) {
 | 
						|
		printk(KERN_ERR "oprofile: CPU%u PMNC still enabled when setup"
 | 
						|
			" new event counter.\n", smp_processor_id());
 | 
						|
		return -EBUSY;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Initialize & Reset PMNC: C bit, D bit and P bit.
 | 
						|
	 *  Note: Using a slower count for CCNT (D bit: divide by 64) results
 | 
						|
	 *   in a more stable system
 | 
						|
	 */
 | 
						|
	armv7_pmnc_write(PMNC_P | PMNC_C | PMNC_D);
 | 
						|
 | 
						|
 | 
						|
	for (cnt = CCNT; cnt < CNTMAX; cnt++) {
 | 
						|
		unsigned long event;
 | 
						|
		u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Disable counter
 | 
						|
		 */
 | 
						|
		armv7_pmnc_disable_counter(cnt);
 | 
						|
		cnt_en[cnt] = 0;
 | 
						|
 | 
						|
		if (!counter_config[cpu_cnt].enabled)
 | 
						|
			continue;
 | 
						|
 | 
						|
		event = counter_config[cpu_cnt].event & 255;
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Set event (if destined for PMNx counters)
 | 
						|
		 * We don't need to set the event if it's a cycle count
 | 
						|
		 */
 | 
						|
		if (cnt != CCNT)
 | 
						|
			armv7_pmnc_write_evtsel(cnt, event);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Enable interrupt for this counter
 | 
						|
		 */
 | 
						|
		armv7_pmnc_enable_intens(cnt);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Reset counter
 | 
						|
		 */
 | 
						|
		armv7_pmnc_reset_counter(cnt);
 | 
						|
 | 
						|
		/*
 | 
						|
		 * Enable counter
 | 
						|
		 */
 | 
						|
		armv7_pmnc_enable_counter(cnt);
 | 
						|
		cnt_en[cnt] = 1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static inline void armv7_start_pmnc(void)
 | 
						|
{
 | 
						|
	armv7_pmnc_write(armv7_pmnc_read() | PMNC_E);
 | 
						|
}
 | 
						|
 | 
						|
static inline void armv7_stop_pmnc(void)
 | 
						|
{
 | 
						|
	armv7_pmnc_write(armv7_pmnc_read() & ~PMNC_E);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * CPU counters' IRQ handler (one IRQ per CPU)
 | 
						|
 */
 | 
						|
static irqreturn_t armv7_pmnc_interrupt(int irq, void *arg)
 | 
						|
{
 | 
						|
	struct pt_regs *regs = get_irq_regs();
 | 
						|
	unsigned int cnt;
 | 
						|
	u32 flags;
 | 
						|
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Stop IRQ generation
 | 
						|
	 */
 | 
						|
	armv7_stop_pmnc();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Get and reset overflow status flags
 | 
						|
	 */
 | 
						|
	flags = armv7_pmnc_getreset_flags();
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Cycle counter
 | 
						|
	 */
 | 
						|
	if (flags & FLAG_C) {
 | 
						|
		u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), CCNT);
 | 
						|
		armv7_pmnc_reset_counter(CCNT);
 | 
						|
		oprofile_add_sample(regs, cpu_cnt);
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * PMNC counters 0:3
 | 
						|
	 */
 | 
						|
	for (cnt = CNT0; cnt < CNTMAX; cnt++) {
 | 
						|
		if (flags & (1 << (cnt - CNT0))) {
 | 
						|
			u32 cpu_cnt = CPU_COUNTER(smp_processor_id(), cnt);
 | 
						|
			armv7_pmnc_reset_counter(cnt);
 | 
						|
			oprofile_add_sample(regs, cpu_cnt);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Allow IRQ generation
 | 
						|
	 */
 | 
						|
	armv7_start_pmnc();
 | 
						|
 | 
						|
	return IRQ_HANDLED;
 | 
						|
}
 | 
						|
 | 
						|
int armv7_request_interrupts(int *irqs, int nr)
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
	int ret = 0;
 | 
						|
 | 
						|
	for (i = 0; i < nr; i++) {
 | 
						|
		ret = request_irq(irqs[i], armv7_pmnc_interrupt,
 | 
						|
				IRQF_DISABLED, "CP15 PMNC", NULL);
 | 
						|
		if (ret != 0) {
 | 
						|
			printk(KERN_ERR "oprofile: unable to request IRQ%u"
 | 
						|
				" for ARMv7\n",
 | 
						|
			       irqs[i]);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (i != nr)
 | 
						|
		while (i-- != 0)
 | 
						|
			free_irq(irqs[i], NULL);
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
void armv7_release_interrupts(int *irqs, int nr)
 | 
						|
{
 | 
						|
	unsigned int i;
 | 
						|
 | 
						|
	for (i = 0; i < nr; i++)
 | 
						|
		free_irq(irqs[i], NULL);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
static void armv7_pmnc_dump_regs(void)
 | 
						|
{
 | 
						|
	u32 val;
 | 
						|
	unsigned int cnt;
 | 
						|
 | 
						|
	printk(KERN_INFO "PMNC registers dump:\n");
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c12, 0" : "=r" (val));
 | 
						|
	printk(KERN_INFO "PMNC  =0x%08x\n", val);
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c12, 1" : "=r" (val));
 | 
						|
	printk(KERN_INFO "CNTENS=0x%08x\n", val);
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c14, 1" : "=r" (val));
 | 
						|
	printk(KERN_INFO "INTENS=0x%08x\n", val);
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c12, 3" : "=r" (val));
 | 
						|
	printk(KERN_INFO "FLAGS =0x%08x\n", val);
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c12, 5" : "=r" (val));
 | 
						|
	printk(KERN_INFO "SELECT=0x%08x\n", val);
 | 
						|
 | 
						|
	asm volatile("mrc p15, 0, %0, c9, c13, 0" : "=r" (val));
 | 
						|
	printk(KERN_INFO "CCNT  =0x%08x\n", val);
 | 
						|
 | 
						|
	for (cnt = CNT0; cnt < CNTMAX; cnt++) {
 | 
						|
		armv7_pmnc_select_counter(cnt);
 | 
						|
		asm volatile("mrc p15, 0, %0, c9, c13, 2" : "=r" (val));
 | 
						|
		printk(KERN_INFO "CNT[%d] count =0x%08x\n", cnt-CNT0, val);
 | 
						|
		asm volatile("mrc p15, 0, %0, c9, c13, 1" : "=r" (val));
 | 
						|
		printk(KERN_INFO "CNT[%d] evtsel=0x%08x\n", cnt-CNT0, val);
 | 
						|
	}
 | 
						|
}
 | 
						|
#endif
 | 
						|
 | 
						|
 | 
						|
static int irqs[] = {
 | 
						|
#ifdef CONFIG_ARCH_OMAP3
 | 
						|
	INT_34XX_BENCH_MPU_EMUL,
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
static void armv7_pmnc_stop(void)
 | 
						|
{
 | 
						|
#ifdef DEBUG
 | 
						|
	armv7_pmnc_dump_regs();
 | 
						|
#endif
 | 
						|
	armv7_stop_pmnc();
 | 
						|
	armv7_release_interrupts(irqs, ARRAY_SIZE(irqs));
 | 
						|
}
 | 
						|
 | 
						|
static int armv7_pmnc_start(void)
 | 
						|
{
 | 
						|
	int ret;
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
	armv7_pmnc_dump_regs();
 | 
						|
#endif
 | 
						|
	ret = armv7_request_interrupts(irqs, ARRAY_SIZE(irqs));
 | 
						|
	if (ret >= 0)
 | 
						|
		armv7_start_pmnc();
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int armv7_detect_pmnc(void)
 | 
						|
{
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
struct op_arm_model_spec op_armv7_spec = {
 | 
						|
	.init		= armv7_detect_pmnc,
 | 
						|
	.num_counters	= 5,
 | 
						|
	.setup_ctrs	= armv7_setup_pmnc,
 | 
						|
	.start		= armv7_pmnc_start,
 | 
						|
	.stop		= armv7_pmnc_stop,
 | 
						|
	.name		= "arm/armv7",
 | 
						|
};
 |