summaryrefslogtreecommitdiffstats
path: root/arch/sparc/oprofile/init.c
blob: c8877a5202b0d504dd0a170a9410ac7ccf90f172 (plain)
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
145
146
/**
 * @file init.c
 *
 * @remark Copyright 2002 OProfile authors
 * @remark Read the file COPYING
 *
 * @author John Levon <levon@movementarian.org>
 */

#include <linux/kernel.h>
#include <linux/oprofile.h>
#include <linux/errno.h>
#include <linux/init.h>
 
#ifdef CONFIG_SPARC64
#include <asm/hypervisor.h>
#include <asm/spitfire.h>
#include <asm/cpudata.h>
#include <asm/irq.h>
#include <asm/pcr.h>

static int nmi_enabled;

/* In order to commonize as much of the implementation as
 * possible, we use PICH as our counter.  Mostly this is
 * to accomodate Niagara-1 which can only count insn cycles
 * in PICH.
 */
static u64 picl_value(void)
{
	u32 delta = local_cpu_data().clock_tick / HZ;

	return ((u64)((0 - delta) & 0xffffffff)) << 32;
}

#define PCR_SUN4U_ENABLE	(PCR_PIC_PRIV | PCR_STRACE | PCR_UTRACE)
#define PCR_N2_ENABLE		(PCR_PIC_PRIV | PCR_STRACE | PCR_UTRACE | \
				 PCR_N2_TOE_OV1 | \
				 (2 << PCR_N2_SL1_SHIFT) | \
				 (0xff << PCR_N2_MASK1_SHIFT))

static u64 pcr_enable;

static void nmi_handler(struct pt_regs *regs)
{
	pcr_ops->write(PCR_PIC_PRIV);

	if (nmi_enabled) {
		oprofile_add_sample(regs, 0);

		write_pic(picl_value());
		pcr_ops->write(pcr_enable);
	}
}

/* We count "clock cycle" events in the lower 32-bit PIC.
 * Then configure it such that it overflows every HZ, and thus
 * generates a level 15 interrupt at that frequency.
 */
static void cpu_nmi_start(void *_unused)
{
	pcr_ops->write(PCR_PIC_PRIV);
	write_pic(picl_value());

	pcr_ops->write(pcr_enable);
}

static void cpu_nmi_stop(void *_unused)
{
	pcr_ops->write(PCR_PIC_PRIV);
}

static int nmi_start(void)
{
	int err = register_perfctr_intr(nmi_handler);

	if (!err) {
		nmi_enabled = 1;
		wmb();
		err = on_each_cpu(cpu_nmi_start, NULL, 1);
		if (err) {
			nmi_enabled = 0;
			wmb();
			on_each_cpu(cpu_nmi_stop, NULL, 1);
			release_perfctr_intr(nmi_handler);
		}
	}

	return err;
}

static void nmi_stop(void)
{
	nmi_enabled = 0;
	wmb();

	on_each_cpu(cpu_nmi_stop, NULL, 1);
	release_perfctr_intr(nmi_handler);
	synchronize_sched();
}

static int oprofile_nmi_init(struct oprofile_operations *ops)
{
	switch (tlb_type) {
	case hypervisor:
		pcr_enable = PCR_N2_ENABLE;
		break;

	case cheetah:
	case cheetah_plus:
		pcr_enable = PCR_SUN4U_ENABLE;
		break;

	default:
		return -ENODEV;
	}

	ops->create_files = NULL;
	ops->setup = NULL;
	ops->shutdown = NULL;
	ops->start = nmi_start;
	ops->stop = nmi_stop;
	ops->cpu_type = "timer";

	printk(KERN_INFO "oprofile: Using perfctr based NMI timer interrupt.\n");

	return 0;
}
#endif

int __init oprofile_arch_init(struct oprofile_operations *ops)
{
	int ret = -ENODEV;

#ifdef CONFIG_SPARC64
	ret = oprofile_nmi_init(ops);
	if (!ret)
		return ret;
#endif

	return ret;
}

void oprofile_arch_exit(void)
{
}