op_model_sh7750.c 7.1 KB
Newer Older
L
Linus Torvalds 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
/*
 * arch/sh/oprofile/op_model_sh7750.c
 *
 * OProfile support for SH7750/SH7750S Performance Counters
 *
 * Copyright (C) 2003, 2004  Paul Mundt
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 */
#include <linux/kernel.h>
#include <linux/oprofile.h>
#include <linux/profile.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/notifier.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define PM_CR_BASE	0xff000084	/* 16-bit */
#define PM_CTR_BASE	0xff100004	/* 32-bit */

#define PMCR1		(PM_CR_BASE  + 0x00)
#define PMCR2		(PM_CR_BASE  + 0x04)
#define PMCTR1H		(PM_CTR_BASE + 0x00)
#define PMCTR1L		(PM_CTR_BASE + 0x04)
#define PMCTR2H		(PM_CTR_BASE + 0x08)
#define PMCTR2L		(PM_CTR_BASE + 0x0c)

#define PMCR_PMM_MASK	0x0000003f

#define PMCR_CLKF	0x00000100
#define PMCR_PMCLR	0x00002000
#define PMCR_PMST	0x00004000
#define PMCR_PMEN	0x00008000

#define PMCR_ENABLE	(PMCR_PMST | PMCR_PMEN)

/*
 * SH7750/SH7750S have 2 perf counters
 */
#define NR_CNTRS	2

extern const char *get_cpu_subtype(void);

struct op_counter_config {
	unsigned long enabled;
	unsigned long event;
	unsigned long count;

	/* Dummy values for userspace tool compliance */
	unsigned long kernel;
	unsigned long user;
	unsigned long unit_mask;
};

static struct op_counter_config ctr[NR_CNTRS];

/*
 * There are a number of events supported by each counter (33 in total).
 * Since we have 2 counters, each counter will take the event code as it
 * corresponds to the PMCR PMM setting. Each counter can be configured
 * independently.
 *
 *	Event Code	Description
 *	----------	-----------
 *
 *	0x01		Operand read access
 *	0x02		Operand write access
 *	0x03		UTLB miss
 *	0x04		Operand cache read miss
 *	0x05		Operand cache write miss
 *	0x06		Instruction fetch (w/ cache)
 *	0x07		Instruction TLB miss
 *	0x08		Instruction cache miss
 *	0x09		All operand accesses
 *	0x0a		All instruction accesses
 *	0x0b		OC RAM operand access
 *	0x0d		On-chip I/O space access
 *	0x0e		Operand access (r/w)
 *	0x0f		Operand cache miss (r/w)
 *	0x10		Branch instruction
 *	0x11		Branch taken
 *	0x12		BSR/BSRF/JSR
 *	0x13		Instruction execution
 *	0x14		Instruction execution in parallel
 *	0x15		FPU Instruction execution
 *	0x16		Interrupt
 *	0x17		NMI
 *	0x18		trapa instruction execution
 *	0x19		UBCA match
 *	0x1a		UBCB match
 *	0x21		Instruction cache fill
 *	0x22		Operand cache fill
 *	0x23		Elapsed time
 *	0x24		Pipeline freeze by I-cache miss
 *	0x25		Pipeline freeze by D-cache miss
 *	0x27		Pipeline freeze by branch instruction
 *	0x28		Pipeline freeze by CPU register
 *	0x29		Pipeline freeze by FPU
 *
 * Unfortunately we don't have a native exception or interrupt for counter
 * overflow (although since these counters can run for 16.3 days without
 * overflowing, it's not really necessary).
 *
 * OProfile on the other hand likes to have samples taken periodically, so
 * for now we just piggyback the timer interrupt to get the expected
 * behavior.
 */

static int sh7750_timer_notify(struct notifier_block *self,
			       unsigned long val, void *regs)
{
	oprofile_add_sample((struct pt_regs *)regs, 0);
	return 0;
}

static struct notifier_block sh7750_timer_notifier = {
	.notifier_call		= sh7750_timer_notify,
};

static u64 sh7750_read_counter(int counter)
{
	u32 hi, lo;

	hi = (counter == 0) ? ctrl_inl(PMCTR1H) : ctrl_inl(PMCTR2H);
	lo = (counter == 0) ? ctrl_inl(PMCTR1L) : ctrl_inl(PMCTR2L);

	return (u64)((u64)(hi & 0xffff) << 32) | lo;
}

/*
 * Files will be in a path like:
 *
 *  /<oprofilefs mount point>/<counter number>/<file>
 *
 * So when dealing with <file>, we look to the parent dentry for the counter
 * number.
 */
static inline int to_counter(struct file *file)
{
J
Josef Sipek 已提交
145
	const unsigned char *name = file->f_path.dentry->d_parent->d_name.name;
L
Linus Torvalds 已提交
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

	return (int)simple_strtol(name, NULL, 10);
}

/*
 * XXX: We have 48-bit counters, so we're probably going to want something
 * more along the lines of oprofilefs_ullong_to_user().. Truncating to
 * unsigned long works fine for now though, as long as we don't attempt to
 * profile for too horribly long.
 */
static ssize_t sh7750_read_count(struct file *file, char __user *buf,
				 size_t count, loff_t *ppos)
{
	int counter = to_counter(file);
	u64 val = sh7750_read_counter(counter);

	return oprofilefs_ulong_to_user((unsigned long)val, buf, count, ppos);
}

static ssize_t sh7750_write_count(struct file *file, const char __user *buf,
				  size_t count, loff_t *ppos)
{
	int counter = to_counter(file);
	unsigned long val;

	if (oprofilefs_ulong_from_user(&val, buf, count))
		return -EFAULT;

	/*
	 * Any write will clear the counter, although only 0 should be
	 * written for this purpose, as we do not support setting the
	 * counter to an arbitrary value.
	 */
	WARN_ON(val != 0);

	if (counter == 0) {
		ctrl_outw(ctrl_inw(PMCR1) | PMCR_PMCLR, PMCR1);
	} else {
		ctrl_outw(ctrl_inw(PMCR2) | PMCR_PMCLR, PMCR2);
	}

	return count;
}

190
static const struct file_operations count_fops = {
L
Linus Torvalds 已提交
191 192 193 194 195 196 197 198 199 200
	.read		= sh7750_read_count,
	.write		= sh7750_write_count,
};

static int sh7750_perf_counter_create_files(struct super_block *sb, struct dentry *root)
{
	int i;

	for (i = 0; i < NR_CNTRS; i++) {
		struct dentry *dir;
201
		char buf[4];
L
Linus Torvalds 已提交
202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261

		snprintf(buf, sizeof(buf), "%d", i);
		dir = oprofilefs_mkdir(sb, root, buf);

		oprofilefs_create_ulong(sb, dir, "enabled", &ctr[i].enabled);
		oprofilefs_create_ulong(sb, dir, "event", &ctr[i].event);
		oprofilefs_create_file(sb, dir, "count", &count_fops);

		/* Dummy entries */
		oprofilefs_create_ulong(sb, dir, "kernel", &ctr[i].kernel);
		oprofilefs_create_ulong(sb, dir, "user", &ctr[i].user);
		oprofilefs_create_ulong(sb, dir, "unit_mask", &ctr[i].unit_mask);
	}

	return 0;
}

static int sh7750_perf_counter_start(void)
{
	u16 pmcr;

	/* Enable counter 1 */
	if (ctr[0].enabled) {
		pmcr = ctrl_inw(PMCR1);
		WARN_ON(pmcr & PMCR_PMEN);

		pmcr &= ~PMCR_PMM_MASK;
		pmcr |= ctr[0].event;
		ctrl_outw(pmcr | PMCR_ENABLE, PMCR1);
	}

	/* Enable counter 2 */
	if (ctr[1].enabled) {
		pmcr = ctrl_inw(PMCR2);
		WARN_ON(pmcr & PMCR_PMEN);

		pmcr &= ~PMCR_PMM_MASK;
		pmcr |= ctr[1].event;
		ctrl_outw(pmcr | PMCR_ENABLE, PMCR2);
	}

	return register_profile_notifier(&sh7750_timer_notifier);
}

static void sh7750_perf_counter_stop(void)
{
	ctrl_outw(ctrl_inw(PMCR1) & ~PMCR_PMEN, PMCR1);
	ctrl_outw(ctrl_inw(PMCR2) & ~PMCR_PMEN, PMCR2);

	unregister_profile_notifier(&sh7750_timer_notifier);
}

static struct oprofile_operations sh7750_perf_counter_ops = {
	.create_files	= sh7750_perf_counter_create_files,
	.start		= sh7750_perf_counter_start,
	.stop		= sh7750_perf_counter_stop,
};

int __init oprofile_arch_init(struct oprofile_operations **ops)
{
262
	if (!(current_cpu_data.flags & CPU_HAS_PERF_COUNTER))
L
Linus Torvalds 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
		return -ENODEV;

	sh7750_perf_counter_ops.cpu_type = (char *)get_cpu_subtype();
	*ops = &sh7750_perf_counter_ops;

	printk(KERN_INFO "oprofile: using SH-4 (%s) performance monitoring.\n",
	       sh7750_perf_counter_ops.cpu_type);

	/* Clear the counters */
	ctrl_outw(ctrl_inw(PMCR1) | PMCR_PMCLR, PMCR1);
	ctrl_outw(ctrl_inw(PMCR2) | PMCR_PMCLR, PMCR2);

	return 0;
}

void oprofile_arch_exit(void)
{
}