1/*	$OpenBSD: pctr.c,v 1.10 2024/04/03 02:01:21 guenther Exp $	*/
2
3/*
4 * Copyright (c) 2007 Mike Belopuhov
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19/*
20 * Pentium performance counter driver for OpenBSD.
21 * Copyright 1996 David Mazieres <dm@lcs.mit.edu>.
22 *
23 * Modification and redistribution in source and binary forms is
24 * permitted provided that due credit is given to the author and the
25 * OpenBSD project by leaving this copyright notice intact.
26 */
27
28#include <sys/param.h>
29#include <sys/errno.h>
30#include <sys/fcntl.h>
31#include <sys/ioccom.h>
32#include <sys/mutex.h>
33#include <sys/systm.h>
34
35#include <machine/intr.h>
36#include <machine/pctr.h>
37#include <machine/cpu.h>
38#include <machine/specialreg.h>
39
40#define PCTR_AMD_NUM	PCTR_NUM
41#define PCTR_INTEL_NUM	2		/* Intel supports only 2 counters */
42#define PCTR_INTEL_VERSION_MASK 0xff
43
44#define usetsc		(cpu_feature & CPUID_TSC)
45#define usepctr		((pctr_isamd && ((cpu_id >> 8) & 15) >= 6) || \
46			    (pctr_isintel && \
47			    (pctr_intel_cap & PCTR_INTEL_VERSION_MASK) >= 1))
48
49int			pctr_isamd;
50int			pctr_isintel;
51uint32_t		pctr_intel_cap;
52
53struct mutex		pctr_conf_lock = MUTEX_INITIALIZER(IPL_HIGH);
54uint32_t		pctr_fn[PCTR_NUM];
55
56static void		pctrrd(struct pctrst *);
57static int		pctrsel(int, uint32_t, uint32_t);
58static void		pctr_enable(struct cpu_info *);
59
60static void
61pctrrd(struct pctrst *st)
62{
63	int i, num, reg;
64
65	num = pctr_isamd ? PCTR_AMD_NUM : PCTR_INTEL_NUM;
66	reg = pctr_isamd ? MSR_K7_EVNTSEL0 : MSR_EVNTSEL0;
67	for (i = 0; i < num; i++)
68		st->pctr_fn[i] = rdmsr(reg + i);
69	__asm volatile("cli");
70	st->pctr_tsc = rdtsc();
71	for (i = 0; i < num; i++)
72		st->pctr_hwc[i] = rdpmc(i);
73	__asm volatile("sti");
74}
75
76void
77pctrattach(int num)
78{
79	struct cpu_info *ci = &cpu_info_primary;
80	uint32_t dummy;
81
82	if (num > 1)
83		return;
84
85	pctr_isamd = (ci->ci_vendor == CPUV_AMD);
86	if (!pctr_isamd) {
87		pctr_isintel = (ci->ci_vendor == CPUV_INTEL);
88		CPUID(0xa, pctr_intel_cap, dummy, dummy, dummy);
89	}
90}
91
92void
93pctr_enable(struct cpu_info *ci)
94{
95	if (usepctr) {
96		/* Enable RDTSC and RDPMC instructions from user-level. */
97		__asm volatile("movq %%cr4,%%rax\n"
98				 "\tandq %0,%%rax\n"
99				 "\torq %1,%%rax\n"
100				 "\tmovq %%rax,%%cr4"
101				 :: "i" (~CR4_TSD), "i" (CR4_PCE) : "rax");
102	} else if (usetsc) {
103		/* Enable RDTSC instruction from user-level. */
104		__asm volatile("movq %%cr4,%%rax\n"
105				 "\tandq %0,%%rax\n"
106				 "\tmovq %%rax,%%cr4"
107				 :: "i" (~CR4_TSD) : "rax");
108	}
109}
110
111int
112pctropen(dev_t dev, int oflags, int devtype, struct proc *p)
113{
114
115	if (minor(dev))
116		return (ENXIO);
117	return (0);
118}
119
120int
121pctrclose(dev_t dev, int oflags, int devtype, struct proc *p)
122{
123
124	return (0);
125}
126
127static int
128pctrsel(int fflag, uint32_t cmd, uint32_t fn)
129{
130	int msrsel, msrval, changed;
131
132	cmd -= PCIOCS0;
133	if (pctr_isamd) {
134		if (cmd > PCTR_AMD_NUM-1)
135			return (EINVAL);
136		msrsel = MSR_K7_EVNTSEL0 + cmd;
137		msrval = MSR_K7_PERFCTR0 + cmd;
138	} else {
139		if (cmd > PCTR_INTEL_NUM-1)
140			return (EINVAL);
141		msrsel = MSR_EVNTSEL0 + cmd;
142		msrval = MSR_PERFCTR0 + cmd;
143	}
144
145	if (!(fflag & FWRITE))
146		return (EPERM);
147	if (fn & 0x380000)
148		return (EINVAL);
149
150	if (fn != 0)
151		pctr_enable(curcpu());
152
153	mtx_enter(&pctr_conf_lock);
154	changed = fn != pctr_fn[cmd];
155	if (changed) {
156		pctr_fn[cmd] = fn;
157		wrmsr(msrval, 0);
158		wrmsr(msrsel, fn);
159		wrmsr(msrval, 0);
160	}
161	mtx_leave(&pctr_conf_lock);
162#ifdef MULTIPROCESSOR
163	if (changed)
164		x86_broadcast_ipi(X86_IPI_PCTR);
165#endif
166
167	return (0);
168}
169
170int
171pctrioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc *p)
172{
173	switch (cmd) {
174	case PCIOCRD:
175	{
176		struct pctrst *st = (struct pctrst *)data;
177
178		if (usepctr)
179			pctrrd(st);
180		else if (usetsc)
181			st->pctr_tsc = rdtsc();
182		return (0);
183	}
184	case PCIOCS0:
185	case PCIOCS1:
186	case PCIOCS2:
187	case PCIOCS3:
188		if (usepctr)
189			return (pctrsel(fflag, cmd, *(u_int *)data));
190		return (ENODEV);
191	default:
192		return (EINVAL);
193	}
194}
195
196void
197pctr_reload(struct cpu_info *ci)
198{
199	int num, i, msrsel, msrval, anyset;
200	uint32_t fn;
201
202	if (pctr_isamd) {
203		num = PCTR_AMD_NUM;
204		msrsel = MSR_K7_EVNTSEL0;
205		msrval = MSR_K7_PERFCTR0;
206	} else {
207		num = PCTR_INTEL_NUM;
208		msrsel = MSR_EVNTSEL0;
209		msrval = MSR_PERFCTR0;
210	}
211
212	anyset = 0;
213	mtx_enter(&pctr_conf_lock);
214	for (i = 0; i < num; i++) {
215		/* only update the ones that don't match */
216		/* XXX generation numbers for zeroing? */
217		fn = rdmsr(msrsel + i);
218		if (fn != pctr_fn[i]) {
219			wrmsr(msrval + i, 0);
220			wrmsr(msrsel + i, pctr_fn[i]);
221			wrmsr(msrval + i, 0);
222		}
223		if (fn)
224			anyset = 1;
225	}
226	mtx_leave(&pctr_conf_lock);
227
228	if (! anyset)
229		pctr_enable(curcpu());
230}
231
232void
233pctr_resume(struct cpu_info *ci)
234{
235	if (usepctr)
236		pctr_reload(ci);
237}
238