| Commit | Line | Data |
|---|---|---|
| 984263bc MD |
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: src/sys/i386/i386/perfmon.c,v 1.21 1999/09/25 18:24:04 phk Exp $ | |
| 870b0161 | 30 | * $DragonFly: src/sys/platform/pc32/i386/perfmon.c,v 1.11 2008/05/10 17:24:07 dillon Exp $ |
| 984263bc MD |
31 | */ |
| 32 | ||
| 33 | #include <sys/param.h> | |
| 34 | #include <sys/systm.h> | |
| 9a5bae71 | 35 | #include <sys/kernel.h> |
| 984263bc | 36 | #include <sys/conf.h> |
| fef8985e | 37 | #include <sys/device.h> |
| 984263bc | 38 | #include <sys/fcntl.h> |
| 545a1cd3 | 39 | #include <sys/lock.h> |
| 984263bc MD |
40 | |
| 41 | #ifndef SMP | |
| 42 | #include <machine/cputypes.h> | |
| 43 | #endif | |
| 44 | #include <machine/clock.h> | |
| 45 | #include <machine/perfmon.h> | |
| 46 | ||
| 47 | static int perfmon_inuse; | |
| 48 | static int perfmon_cpuok; | |
| 49 | #ifndef SMP | |
| 50 | static int msr_ctl[NPMC]; | |
| 51 | #endif | |
| 52 | static int msr_pmc[NPMC]; | |
| 53 | static unsigned int ctl_shadow[NPMC]; | |
| 54 | static quad_t pmc_shadow[NPMC]; /* used when ctr is stopped on P5 */ | |
| 55 | static int (*writectl)(int); | |
| 56 | #ifndef SMP | |
| 57 | static int writectl5(int); | |
| 58 | static int writectl6(int); | |
| 59 | #endif | |
| 60 | ||
| fef8985e MD |
61 | static d_close_t perfmon_close; |
| 62 | static d_open_t perfmon_open; | |
| 63 | static d_ioctl_t perfmon_ioctl; | |
| 984263bc MD |
64 | |
| 65 | #define CDEV_MAJOR 2 /* We're really a minor of mem.c */ | |
| fef8985e MD |
66 | static struct dev_ops perfmon_ops = { |
| 67 | { "perfmon", CDEV_MAJOR, 0 }, | |
| 68 | .d_open = perfmon_open, | |
| 69 | .d_close = perfmon_close, | |
| 70 | .d_ioctl = perfmon_ioctl, | |
| 984263bc MD |
71 | }; |
| 72 | ||
| 73 | /* | |
| 9a5bae71 MD |
74 | * Initialize the device ops for user access to the perfmon. This must |
| 75 | * be done late in the boot sequence. | |
| 76 | * | |
| 77 | * NOTE: The perfmon is really a minor of the mem major. Perfmon | |
| 78 | * gets 32-47. | |
| 79 | */ | |
| 80 | static void | |
| 81 | perfmon_driver_init(void *unused __unused) | |
| 82 | { | |
| 9a5bae71 MD |
83 | make_dev(&perfmon_ops, 32, UID_ROOT, GID_KMEM, 0640, "perfmon"); |
| 84 | } | |
| 85 | ||
| 86 | SYSINIT(perfmondrv, SI_SUB_DRIVERS, SI_ORDER_ANY, perfmon_driver_init, NULL) | |
| 87 | ||
| 88 | /* | |
| 89 | * This is called in early boot, after cpu_class has been set up. | |
| 984263bc MD |
90 | */ |
| 91 | void | |
| 92 | perfmon_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 */ | |
| 984263bc MD |
118 | } |
| 119 | ||
| 120 | int | |
| 121 | perfmon_avail(void) | |
| 122 | { | |
| 123 | return perfmon_cpuok; | |
| 124 | } | |
| 125 | ||
| 126 | int | |
| 127 | perfmon_setup(int pmc, unsigned int control) | |
| 128 | { | |
| 129 | if (pmc < 0 || pmc >= NPMC) | |
| 130 | return EINVAL; | |
| 131 | ||
| 132 | perfmon_inuse |= (1 << pmc); | |
| 133 | control &= ~(PMCF_SYS_FLAGS << 16); | |
| 8a8d5d85 | 134 | mpintr_lock(); /* doesn't have to be mpintr_lock YYY */ |
| 984263bc MD |
135 | ctl_shadow[pmc] = control; |
| 136 | writectl(pmc); | |
| 137 | wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0); | |
| 8a8d5d85 | 138 | mpintr_unlock(); |
| 984263bc MD |
139 | return 0; |
| 140 | } | |
| 141 | ||
| 142 | int | |
| 143 | perfmon_get(int pmc, unsigned int *control) | |
| 144 | { | |
| 145 | if (pmc < 0 || pmc >= NPMC) | |
| 146 | return EINVAL; | |
| 147 | ||
| 148 | if (perfmon_inuse & (1 << pmc)) { | |
| 149 | *control = ctl_shadow[pmc]; | |
| 150 | return 0; | |
| 151 | } | |
| 152 | return EBUSY; /* XXX reversed sense */ | |
| 153 | } | |
| 154 | ||
| 155 | int | |
| 156 | perfmon_fini(int pmc) | |
| 157 | { | |
| 158 | if (pmc < 0 || pmc >= NPMC) | |
| 159 | return EINVAL; | |
| 160 | ||
| 161 | if (perfmon_inuse & (1 << pmc)) { | |
| 162 | perfmon_stop(pmc); | |
| 163 | ctl_shadow[pmc] = 0; | |
| 164 | perfmon_inuse &= ~(1 << pmc); | |
| 165 | return 0; | |
| 166 | } | |
| 167 | return EBUSY; /* XXX reversed sense */ | |
| 168 | } | |
| 169 | ||
| 170 | int | |
| 171 | perfmon_start(int pmc) | |
| 172 | { | |
| 173 | if (pmc < 0 || pmc >= NPMC) | |
| 174 | return EINVAL; | |
| 175 | ||
| 176 | if (perfmon_inuse & (1 << pmc)) { | |
| 8a8d5d85 | 177 | mpintr_lock(); /* doesn't have to be mpintr YYY */ |
| 984263bc MD |
178 | ctl_shadow[pmc] |= (PMCF_EN << 16); |
| 179 | wrmsr(msr_pmc[pmc], pmc_shadow[pmc]); | |
| 180 | writectl(pmc); | |
| 8a8d5d85 | 181 | mpintr_unlock(); |
| 984263bc MD |
182 | return 0; |
| 183 | } | |
| 184 | return EBUSY; | |
| 185 | } | |
| 186 | ||
| 187 | int | |
| 188 | perfmon_stop(int pmc) | |
| 189 | { | |
| 190 | if (pmc < 0 || pmc >= NPMC) | |
| 191 | return EINVAL; | |
| 192 | ||
| 193 | if (perfmon_inuse & (1 << pmc)) { | |
| 8a8d5d85 | 194 | mpintr_lock(); |
| 984263bc MD |
195 | pmc_shadow[pmc] = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL; |
| 196 | ctl_shadow[pmc] &= ~(PMCF_EN << 16); | |
| 197 | writectl(pmc); | |
| 8a8d5d85 | 198 | mpintr_unlock(); |
| 984263bc MD |
199 | return 0; |
| 200 | } | |
| 201 | return EBUSY; | |
| 202 | } | |
| 203 | ||
| 204 | int | |
| 205 | perfmon_read(int pmc, quad_t *val) | |
| 206 | { | |
| 207 | if (pmc < 0 || pmc >= NPMC) | |
| 208 | return EINVAL; | |
| 209 | ||
| 210 | if (perfmon_inuse & (1 << pmc)) { | |
| 211 | if (ctl_shadow[pmc] & (PMCF_EN << 16)) | |
| 212 | *val = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL; | |
| 213 | else | |
| 214 | *val = pmc_shadow[pmc]; | |
| 215 | return 0; | |
| 216 | } | |
| 217 | ||
| 218 | return EBUSY; | |
| 219 | } | |
| 220 | ||
| 221 | int | |
| 222 | perfmon_reset(int pmc) | |
| 223 | { | |
| 224 | if (pmc < 0 || pmc >= NPMC) | |
| 225 | return EINVAL; | |
| 226 | ||
| 227 | if (perfmon_inuse & (1 << pmc)) { | |
| 228 | wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0); | |
| 229 | return 0; | |
| 230 | } | |
| 231 | return EBUSY; | |
| 232 | } | |
| 233 | ||
| 234 | #ifndef SMP | |
| 235 | /* | |
| 236 | * Unfortunately, the performance-monitoring registers are laid out | |
| 237 | * differently in the P5 and P6. We keep everything in P6 format | |
| 238 | * internally (except for the event code), and convert to P5 | |
| 239 | * format as needed on those CPUs. The writectl function pointer | |
| 240 | * is set up to point to one of these functions by perfmon_init(). | |
| 241 | */ | |
| 242 | int | |
| 243 | writectl6(int pmc) | |
| 244 | { | |
| 245 | if (pmc > 0 && !(ctl_shadow[pmc] & (PMCF_EN << 16))) { | |
| 246 | wrmsr(msr_ctl[pmc], 0); | |
| 247 | } else { | |
| 248 | wrmsr(msr_ctl[pmc], ctl_shadow[pmc]); | |
| 249 | } | |
| 250 | return 0; | |
| 251 | } | |
| 252 | ||
| 253 | #define P5FLAG_P 0x200 | |
| 254 | #define P5FLAG_E 0x100 | |
| 255 | #define P5FLAG_USR 0x80 | |
| 256 | #define P5FLAG_OS 0x40 | |
| 257 | ||
| 258 | int | |
| 259 | writectl5(int pmc) | |
| 260 | { | |
| 261 | quad_t newval = 0; | |
| 262 | ||
| 263 | if (ctl_shadow[1] & (PMCF_EN << 16)) { | |
| 264 | if (ctl_shadow[1] & (PMCF_USR << 16)) | |
| 265 | newval |= P5FLAG_USR << 16; | |
| 266 | if (ctl_shadow[1] & (PMCF_OS << 16)) | |
| 267 | newval |= P5FLAG_OS << 16; | |
| 268 | if (!(ctl_shadow[1] & (PMCF_E << 16))) | |
| 269 | newval |= P5FLAG_E << 16; | |
| 270 | newval |= (ctl_shadow[1] & 0x3f) << 16; | |
| 271 | } | |
| 272 | if (ctl_shadow[0] & (PMCF_EN << 16)) { | |
| 273 | if (ctl_shadow[0] & (PMCF_USR << 16)) | |
| 274 | newval |= P5FLAG_USR; | |
| 275 | if (ctl_shadow[0] & (PMCF_OS << 16)) | |
| 276 | newval |= P5FLAG_OS; | |
| 277 | if (!(ctl_shadow[0] & (PMCF_E << 16))) | |
| 278 | newval |= P5FLAG_E; | |
| 279 | newval |= ctl_shadow[0] & 0x3f; | |
| 280 | } | |
| 281 | ||
| 282 | wrmsr(msr_ctl[0], newval); | |
| 283 | return 0; /* XXX should check for unimplemented bits */ | |
| 284 | } | |
| 285 | #endif /* !SMP */ | |
| 286 | ||
| 287 | /* | |
| 288 | * Now the user-mode interface, called from a subdevice of mem.c. | |
| 289 | */ | |
| 290 | static int writer; | |
| 291 | static int writerpmc; | |
| 292 | ||
| 293 | static int | |
| fef8985e | 294 | perfmon_open(struct dev_open_args *ap) |
| 984263bc MD |
295 | { |
| 296 | if (!perfmon_cpuok) | |
| 297 | return ENXIO; | |
| 298 | ||
| fef8985e | 299 | if (ap->a_oflags & FWRITE) { |
| 984263bc MD |
300 | if (writer) { |
| 301 | return EBUSY; | |
| 302 | } else { | |
| 303 | writer = 1; | |
| 304 | writerpmc = 0; | |
| 305 | } | |
| 306 | } | |
| 307 | return 0; | |
| 308 | } | |
| 309 | ||
| 310 | static int | |
| fef8985e | 311 | perfmon_close(struct dev_close_args *ap) |
| 984263bc | 312 | { |
| fef8985e | 313 | if (ap->a_fflag & FWRITE) { |
| 984263bc MD |
314 | int i; |
| 315 | ||
| 316 | for (i = 0; i < NPMC; i++) { | |
| 317 | if (writerpmc & (1 << i)) | |
| 318 | perfmon_fini(i); | |
| 319 | } | |
| 320 | writer = 0; | |
| 321 | } | |
| 322 | return 0; | |
| 323 | } | |
| 324 | ||
| 325 | static int | |
| fef8985e | 326 | perfmon_ioctl(struct dev_ioctl_args *ap) |
| 984263bc | 327 | { |
| fef8985e | 328 | caddr_t param = ap->a_data; |
| 984263bc MD |
329 | struct pmc *pmc; |
| 330 | struct pmc_data *pmcd; | |
| 331 | struct pmc_tstamp *pmct; | |
| 332 | int *ip; | |
| 333 | int rv; | |
| 334 | ||
| fef8985e | 335 | switch(ap->a_cmd) { |
| 984263bc | 336 | case PMIOSETUP: |
| fef8985e | 337 | if (!(ap->a_fflag & FWRITE)) |
| 984263bc MD |
338 | return EPERM; |
| 339 | pmc = (struct pmc *)param; | |
| 340 | ||
| 341 | rv = perfmon_setup(pmc->pmc_num, pmc->pmc_val); | |
| 342 | if (!rv) { | |
| 343 | writerpmc |= (1 << pmc->pmc_num); | |
| 344 | } | |
| 345 | break; | |
| 346 | ||
| 347 | case PMIOGET: | |
| 348 | pmc = (struct pmc *)param; | |
| 349 | rv = perfmon_get(pmc->pmc_num, &pmc->pmc_val); | |
| 350 | break; | |
| 351 | ||
| 352 | case PMIOSTART: | |
| fef8985e | 353 | if (!(ap->a_fflag & FWRITE)) |
| 984263bc MD |
354 | return EPERM; |
| 355 | ||
| 356 | ip = (int *)param; | |
| 357 | rv = perfmon_start(*ip); | |
| 358 | break; | |
| 359 | ||
| 360 | case PMIOSTOP: | |
| fef8985e | 361 | if (!(ap->a_fflag & FWRITE)) |
| 984263bc MD |
362 | return EPERM; |
| 363 | ||
| 364 | ip = (int *)param; | |
| 365 | rv = perfmon_stop(*ip); | |
| 366 | break; | |
| 367 | ||
| 368 | case PMIORESET: | |
| fef8985e | 369 | if (!(ap->a_fflag & FWRITE)) |
| 984263bc MD |
370 | return EPERM; |
| 371 | ||
| 372 | ip = (int *)param; | |
| 373 | rv = perfmon_reset(*ip); | |
| 374 | break; | |
| 375 | ||
| 376 | case PMIOREAD: | |
| 377 | pmcd = (struct pmc_data *)param; | |
| 378 | rv = perfmon_read(pmcd->pmcd_num, &pmcd->pmcd_value); | |
| 379 | break; | |
| 380 | ||
| 381 | case PMIOTSTAMP: | |
| 870b0161 | 382 | if (tsc_frequency == 0) { |
| 984263bc MD |
383 | rv = ENOTTY; |
| 384 | break; | |
| 385 | } | |
| 386 | pmct = (struct pmc_tstamp *)param; | |
| 387 | /* XXX interface loses precision. */ | |
| 870b0161 | 388 | pmct->pmct_rate = (int)(tsc_frequency / 1000000); |
| 984263bc MD |
389 | pmct->pmct_value = rdtsc(); |
| 390 | rv = 0; | |
| 391 | break; | |
| 392 | default: | |
| 393 | rv = ENOTTY; | |
| 394 | } | |
| 395 | ||
| 396 | return rv; | |
| 397 | } |