perfmon.c revision 92649
1/*
2 * Copyright 1996 Massachusetts Institute of Technology
3 *
4 * Permission to use, copy, modify, and distribute this software and
5 * its documentation for any purpose and without fee is hereby
6 * granted, provided that both the above copyright notice and this
7 * permission notice appear in all copies, that both the above
8 * copyright notice and this permission notice appear in all
9 * supporting documentation, and that the name of M.I.T. not be used
10 * in advertising or publicity pertaining to distribution of the
11 * software without specific, written prior permission.  M.I.T. makes
12 * no representations about the suitability of this software for any
13 * purpose.  It is provided "as is" without express or implied
14 * warranty.
15 *
16 * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
17 * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
18 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
20 * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 *
29 * $FreeBSD: head/sys/i386/i386/perfmon.c 92649 2002-03-19 06:14:34Z alfred $
30 */
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/conf.h>
35#include <sys/fcntl.h>
36
37#ifndef SMP
38#include <machine/cputypes.h>
39#endif
40#include <machine/clock.h>
41#include <machine/perfmon.h>
42
43static int perfmon_inuse;
44static int perfmon_cpuok;
45#ifndef SMP
46static int msr_ctl[NPMC];
47#endif
48static int msr_pmc[NPMC];
49static unsigned int ctl_shadow[NPMC];
50static quad_t pmc_shadow[NPMC];	/* used when ctr is stopped on P5 */
51static int (*writectl)(int);
52#ifndef SMP
53static int writectl5(int);
54static int writectl6(int);
55#endif
56
57static d_close_t perfmon_close;
58static d_open_t	perfmon_open;
59static d_ioctl_t perfmon_ioctl;
60
61/*
62 * XXX perfmon_init_dev(void *) is a split from the perfmon_init() funtion.
63 * This solves a problem for DEVFS users.  It loads the "perfmon" driver after
64 * the DEVFS subsystem has been kicked into action.  The SI_ORDER_ANY is to
65 * assure that it is the most lowest priority task which, guarantees the
66 * above.
67 */
68static void perfmon_init_dev __P((void *));
69SYSINIT(cpu, SI_SUB_DRIVERS, SI_ORDER_ANY, perfmon_init_dev, NULL);
70
71#define CDEV_MAJOR 2	/* We're really a minor of mem.c */
72static struct cdevsw perfmon_cdevsw = {
73	/* open */      perfmon_open,
74	/* close */     perfmon_close,
75	/* read */      noread,
76	/* write */     nowrite,
77	/* ioctl */     perfmon_ioctl,
78	/* poll */      nopoll,
79	/* mmap */      nommap,
80	/* strategy */  nostrategy,
81	/* name */      "perfmon",
82	/* maj */       CDEV_MAJOR,
83	/* dump */      nodump,
84	/* psize */     nopsize,
85	/* flags */     0,
86};
87
88/*
89 * Must be called after cpu_class is set up.
90 */
91void
92perfmon_init(void)
93{
94#ifndef SMP
95	switch(cpu_class) {
96	case CPUCLASS_586:
97		perfmon_cpuok = 1;
98		msr_ctl[0] = 0x11;
99		msr_ctl[1] = 0x11;
100		msr_pmc[0] = 0x12;
101		msr_pmc[1] = 0x13;
102		writectl = writectl5;
103		break;
104	case CPUCLASS_686:
105		perfmon_cpuok = 1;
106		msr_ctl[0] = 0x186;
107		msr_ctl[1] = 0x187;
108		msr_pmc[0] = 0xc1;
109		msr_pmc[1] = 0xc2;
110		writectl = writectl6;
111		break;
112
113	default:
114		perfmon_cpuok = 0;
115		break;
116	}
117#endif /* SMP */
118}
119
120static void
121perfmon_init_dev(dummy)
122	void *dummy;
123{
124	make_dev(&perfmon_cdevsw, 32, UID_ROOT, GID_KMEM, 0640, "perfmon");
125}
126
127int
128perfmon_avail(void)
129{
130	return perfmon_cpuok;
131}
132
133int
134perfmon_setup(int pmc, unsigned int control)
135{
136	critical_t	savecrit;
137
138	if (pmc < 0 || pmc >= NPMC)
139		return EINVAL;
140
141	perfmon_inuse |= (1 << pmc);
142	control &= ~(PMCF_SYS_FLAGS << 16);
143	savecrit = cpu_critical_enter();
144	ctl_shadow[pmc] = control;
145	writectl(pmc);
146	wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0);
147	cpu_critical_exit(savecrit);
148	return 0;
149}
150
151int
152perfmon_get(int pmc, unsigned int *control)
153{
154	if (pmc < 0 || pmc >= NPMC)
155		return EINVAL;
156
157	if (perfmon_inuse & (1 << pmc)) {
158		*control = ctl_shadow[pmc];
159		return 0;
160	}
161	return EBUSY;		/* XXX reversed sense */
162}
163
164int
165perfmon_fini(int pmc)
166{
167	if (pmc < 0 || pmc >= NPMC)
168		return EINVAL;
169
170	if (perfmon_inuse & (1 << pmc)) {
171		perfmon_stop(pmc);
172		ctl_shadow[pmc] = 0;
173		perfmon_inuse &= ~(1 << pmc);
174		return 0;
175	}
176	return EBUSY;		/* XXX reversed sense */
177}
178
179int
180perfmon_start(int pmc)
181{
182	critical_t	savecrit;
183
184	if (pmc < 0 || pmc >= NPMC)
185		return EINVAL;
186
187	if (perfmon_inuse & (1 << pmc)) {
188		savecrit = cpu_critical_enter();
189		ctl_shadow[pmc] |= (PMCF_EN << 16);
190		wrmsr(msr_pmc[pmc], pmc_shadow[pmc]);
191		writectl(pmc);
192		cpu_critical_exit(savecrit);
193		return 0;
194	}
195	return EBUSY;
196}
197
198int
199perfmon_stop(int pmc)
200{
201	critical_t	savecrit;
202
203	if (pmc < 0 || pmc >= NPMC)
204		return EINVAL;
205
206	if (perfmon_inuse & (1 << pmc)) {
207		savecrit = cpu_critical_enter();
208		pmc_shadow[pmc] = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL;
209		ctl_shadow[pmc] &= ~(PMCF_EN << 16);
210		writectl(pmc);
211		cpu_critical_exit(savecrit);
212		return 0;
213	}
214	return EBUSY;
215}
216
217int
218perfmon_read(int pmc, quad_t *val)
219{
220	if (pmc < 0 || pmc >= NPMC)
221		return EINVAL;
222
223	if (perfmon_inuse & (1 << pmc)) {
224		if (ctl_shadow[pmc] & (PMCF_EN << 16))
225			*val = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL;
226		else
227			*val = pmc_shadow[pmc];
228		return 0;
229	}
230
231	return EBUSY;
232}
233
234int
235perfmon_reset(int pmc)
236{
237	if (pmc < 0 || pmc >= NPMC)
238		return EINVAL;
239
240	if (perfmon_inuse & (1 << pmc)) {
241		wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0);
242		return 0;
243	}
244	return EBUSY;
245}
246
247#ifndef SMP
248/*
249 * Unfortunately, the performance-monitoring registers are laid out
250 * differently in the P5 and P6.  We keep everything in P6 format
251 * internally (except for the event code), and convert to P5
252 * format as needed on those CPUs.  The writectl function pointer
253 * is set up to point to one of these functions by perfmon_init().
254 */
255int
256writectl6(int pmc)
257{
258	if (pmc > 0 && !(ctl_shadow[pmc] & (PMCF_EN << 16))) {
259		wrmsr(msr_ctl[pmc], 0);
260	} else {
261		wrmsr(msr_ctl[pmc], ctl_shadow[pmc]);
262	}
263	return 0;
264}
265
266#define	P5FLAG_P	0x200
267#define	P5FLAG_E	0x100
268#define	P5FLAG_USR	0x80
269#define	P5FLAG_OS	0x40
270
271int
272writectl5(int pmc)
273{
274	quad_t newval = 0;
275
276	if (ctl_shadow[1] & (PMCF_EN << 16)) {
277		if (ctl_shadow[1] & (PMCF_USR << 16))
278			newval |= P5FLAG_USR << 16;
279		if (ctl_shadow[1] & (PMCF_OS << 16))
280			newval |= P5FLAG_OS << 16;
281		if (!(ctl_shadow[1] & (PMCF_E << 16)))
282			newval |= P5FLAG_E << 16;
283		newval |= (ctl_shadow[1] & 0x3f) << 16;
284	}
285	if (ctl_shadow[0] & (PMCF_EN << 16)) {
286		if (ctl_shadow[0] & (PMCF_USR << 16))
287			newval |= P5FLAG_USR;
288		if (ctl_shadow[0] & (PMCF_OS << 16))
289			newval |= P5FLAG_OS;
290		if (!(ctl_shadow[0] & (PMCF_E << 16)))
291			newval |= P5FLAG_E;
292		newval |= ctl_shadow[0] & 0x3f;
293	}
294
295	wrmsr(msr_ctl[0], newval);
296	return 0;		/* XXX should check for unimplemented bits */
297}
298#endif /* !SMP */
299
300/*
301 * Now the user-mode interface, called from a subdevice of mem.c.
302 */
303static int writer;
304static int writerpmc;
305
306static int
307perfmon_open(dev_t dev, int flags, int fmt, struct thread *td)
308{
309	if (!perfmon_cpuok)
310		return ENXIO;
311
312	if (flags & FWRITE) {
313		if (writer) {
314			return EBUSY;
315		} else {
316			writer = 1;
317			writerpmc = 0;
318		}
319	}
320	return 0;
321}
322
323static int
324perfmon_close(dev_t dev, int flags, int fmt, struct thread *td)
325{
326	if (flags & FWRITE) {
327		int i;
328
329		for (i = 0; i < NPMC; i++) {
330			if (writerpmc & (1 << i))
331				perfmon_fini(i);
332		}
333		writer = 0;
334	}
335	return 0;
336}
337
338static int
339perfmon_ioctl(dev_t dev, u_long cmd, caddr_t param, int flags, struct thread *td)
340{
341	struct pmc *pmc;
342	struct pmc_data *pmcd;
343	struct pmc_tstamp *pmct;
344	int *ip;
345	int rv;
346
347	switch(cmd) {
348	case PMIOSETUP:
349		if (!(flags & FWRITE))
350			return EPERM;
351		pmc = (struct pmc *)param;
352
353		rv = perfmon_setup(pmc->pmc_num, pmc->pmc_val);
354		if (!rv) {
355			writerpmc |= (1 << pmc->pmc_num);
356		}
357		break;
358
359	case PMIOGET:
360		pmc = (struct pmc *)param;
361		rv = perfmon_get(pmc->pmc_num, &pmc->pmc_val);
362		break;
363
364	case PMIOSTART:
365		if (!(flags & FWRITE))
366			return EPERM;
367
368		ip = (int *)param;
369		rv = perfmon_start(*ip);
370		break;
371
372	case PMIOSTOP:
373		if (!(flags & FWRITE))
374			return EPERM;
375
376		ip = (int *)param;
377		rv = perfmon_stop(*ip);
378		break;
379
380	case PMIORESET:
381		if (!(flags & FWRITE))
382			return EPERM;
383
384		ip = (int *)param;
385		rv = perfmon_reset(*ip);
386		break;
387
388	case PMIOREAD:
389		pmcd = (struct pmc_data *)param;
390		rv = perfmon_read(pmcd->pmcd_num, &pmcd->pmcd_value);
391		break;
392
393	case PMIOTSTAMP:
394		if (!tsc_freq) {
395			rv = ENOTTY;
396			break;
397		}
398		pmct = (struct pmc_tstamp *)param;
399		/* XXX interface loses precision. */
400		pmct->pmct_rate = tsc_freq / 1000000;
401		pmct->pmct_value = rdtsc();
402		rv = 0;
403		break;
404	default:
405		rv = ENOTTY;
406	}
407
408	return rv;
409}
410