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
49 int pctr_isamd;
50 int pctr_isintel;
51 uint32_t pctr_intel_cap;
52
53 struct mutex pctr_conf_lock = MUTEX_INITIALIZER(IPL_HIGH);
54 uint32_t pctr_fn[PCTR_NUM];
55
56 static void pctrrd(struct pctrst *);
57 static int pctrsel(int, uint32_t, uint32_t);
58 static void pctr_enable(struct cpu_info *);
59
60 static void
pctrrd(struct pctrst * st)61 pctrrd(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
76 void
pctrattach(int num)77 pctrattach(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
92 void
pctr_enable(struct cpu_info * ci)93 pctr_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
111 int
pctropen(dev_t dev,int oflags,int devtype,struct proc * p)112 pctropen(dev_t dev, int oflags, int devtype, struct proc *p)
113 {
114
115 if (minor(dev))
116 return (ENXIO);
117 return (0);
118 }
119
120 int
pctrclose(dev_t dev,int oflags,int devtype,struct proc * p)121 pctrclose(dev_t dev, int oflags, int devtype, struct proc *p)
122 {
123
124 return (0);
125 }
126
127 static int
pctrsel(int fflag,uint32_t cmd,uint32_t fn)128 pctrsel(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
170 int
pctrioctl(dev_t dev,u_long cmd,caddr_t data,int fflag,struct proc * p)171 pctrioctl(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
196 void
pctr_reload(struct cpu_info * ci)197 pctr_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
232 void
pctr_resume(struct cpu_info * ci)233 pctr_resume(struct cpu_info *ci)
234 {
235 if (usepctr)
236 pctr_reload(ci);
237 }
238