perfmon.c revision 126076
1144513Simp/*- 2144513Simp * Copyright 1996 Massachusetts Institute of Technology 3144513Simp * 4144513Simp * Permission to use, copy, modify, and distribute this software and 5144513Simp * its documentation for any purpose and without fee is hereby 6232261Stijl * 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 30#include <sys/cdefs.h> 31__FBSDID("$FreeBSD: head/sys/i386/i386/perfmon.c 126076 2004-02-21 19:42:58Z phk $"); 32 33#include <sys/param.h> 34#include <sys/systm.h> 35#include <sys/conf.h> 36#include <sys/fcntl.h> 37#include <sys/kernel.h> 38 39#ifndef SMP 40#include <machine/cputypes.h> 41#endif 42#include <machine/clock.h> 43#include <machine/perfmon.h> 44#include <machine/specialreg.h> 45 46static int perfmon_inuse; 47static int perfmon_cpuok; 48#ifndef SMP 49static int msr_ctl[NPMC]; 50#endif 51static int msr_pmc[NPMC]; 52static unsigned int ctl_shadow[NPMC]; 53static quad_t pmc_shadow[NPMC]; /* used when ctr is stopped on P5 */ 54static int (*writectl)(int); 55#ifndef SMP 56static int writectl5(int); 57static int writectl6(int); 58#endif 59 60static d_close_t perfmon_close; 61static d_open_t perfmon_open; 62static d_ioctl_t perfmon_ioctl; 63 64/* 65 * XXX perfmon_init_dev(void *) is a split from the perfmon_init() funtion. 66 * This solves a problem for DEVFS users. It loads the "perfmon" driver after 67 * the DEVFS subsystem has been kicked into action. The SI_ORDER_ANY is to 68 * assure that it is the most lowest priority task which, guarantees the 69 * above. 70 */ 71static void perfmon_init_dev(void *); 72SYSINIT(cpu, SI_SUB_DRIVERS, SI_ORDER_ANY, perfmon_init_dev, NULL); 73 74static struct cdevsw perfmon_cdevsw = { 75 .d_open = perfmon_open, 76 .d_close = perfmon_close, 77 .d_ioctl = perfmon_ioctl, 78 .d_name = "perfmon", 79}; 80 81/* 82 * Must be called after cpu_class is set up. 83 */ 84void 85perfmon_init(void) 86{ 87#ifndef SMP 88 switch(cpu_class) { 89 case CPUCLASS_586: 90 perfmon_cpuok = 1; 91 msr_ctl[0] = MSR_P5_CESR; 92 msr_ctl[1] = MSR_P5_CESR; 93 msr_pmc[0] = MSR_P5_CTR0; 94 msr_pmc[1] = MSR_P5_CTR1; 95 writectl = writectl5; 96 break; 97 case CPUCLASS_686: 98 perfmon_cpuok = 1; 99 msr_ctl[0] = MSR_EVNTSEL0; 100 msr_ctl[1] = MSR_EVNTSEL1; 101 msr_pmc[0] = MSR_PERFCTR0; 102 msr_pmc[1] = MSR_PERFCTR1; 103 writectl = writectl6; 104 break; 105 106 default: 107 perfmon_cpuok = 0; 108 break; 109 } 110#endif /* SMP */ 111} 112 113static void 114perfmon_init_dev(dummy) 115 void *dummy; 116{ 117 make_dev(&perfmon_cdevsw, 32, UID_ROOT, GID_KMEM, 0640, "perfmon"); 118} 119 120int 121perfmon_avail(void) 122{ 123 return perfmon_cpuok; 124} 125 126int 127perfmon_setup(int pmc, unsigned int control) 128{ 129 register_t savecrit; 130 131 if (pmc < 0 || pmc >= NPMC) 132 return EINVAL; 133 134 perfmon_inuse |= (1 << pmc); 135 control &= ~(PMCF_SYS_FLAGS << 16); 136 savecrit = intr_disable(); 137 ctl_shadow[pmc] = control; 138 writectl(pmc); 139 wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0); 140 intr_restore(savecrit); 141 return 0; 142} 143 144int 145perfmon_get(int pmc, unsigned int *control) 146{ 147 if (pmc < 0 || pmc >= NPMC) 148 return EINVAL; 149 150 if (perfmon_inuse & (1 << pmc)) { 151 *control = ctl_shadow[pmc]; 152 return 0; 153 } 154 return EBUSY; /* XXX reversed sense */ 155} 156 157int 158perfmon_fini(int pmc) 159{ 160 if (pmc < 0 || pmc >= NPMC) 161 return EINVAL; 162 163 if (perfmon_inuse & (1 << pmc)) { 164 perfmon_stop(pmc); 165 ctl_shadow[pmc] = 0; 166 perfmon_inuse &= ~(1 << pmc); 167 return 0; 168 } 169 return EBUSY; /* XXX reversed sense */ 170} 171 172int 173perfmon_start(int pmc) 174{ 175 register_t savecrit; 176 177 if (pmc < 0 || pmc >= NPMC) 178 return EINVAL; 179 180 if (perfmon_inuse & (1 << pmc)) { 181 savecrit = intr_disable(); 182 ctl_shadow[pmc] |= (PMCF_EN << 16); 183 wrmsr(msr_pmc[pmc], pmc_shadow[pmc]); 184 writectl(pmc); 185 intr_restore(savecrit); 186 return 0; 187 } 188 return EBUSY; 189} 190 191int 192perfmon_stop(int pmc) 193{ 194 register_t savecrit; 195 196 if (pmc < 0 || pmc >= NPMC) 197 return EINVAL; 198 199 if (perfmon_inuse & (1 << pmc)) { 200 savecrit = intr_disable(); 201 pmc_shadow[pmc] = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL; 202 ctl_shadow[pmc] &= ~(PMCF_EN << 16); 203 writectl(pmc); 204 intr_restore(savecrit); 205 return 0; 206 } 207 return EBUSY; 208} 209 210int 211perfmon_read(int pmc, quad_t *val) 212{ 213 if (pmc < 0 || pmc >= NPMC) 214 return EINVAL; 215 216 if (perfmon_inuse & (1 << pmc)) { 217 if (ctl_shadow[pmc] & (PMCF_EN << 16)) 218 *val = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL; 219 else 220 *val = pmc_shadow[pmc]; 221 return 0; 222 } 223 224 return EBUSY; 225} 226 227int 228perfmon_reset(int pmc) 229{ 230 if (pmc < 0 || pmc >= NPMC) 231 return EINVAL; 232 233 if (perfmon_inuse & (1 << pmc)) { 234 wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0); 235 return 0; 236 } 237 return EBUSY; 238} 239 240#ifndef SMP 241/* 242 * Unfortunately, the performance-monitoring registers are laid out 243 * differently in the P5 and P6. We keep everything in P6 format 244 * internally (except for the event code), and convert to P5 245 * format as needed on those CPUs. The writectl function pointer 246 * is set up to point to one of these functions by perfmon_init(). 247 */ 248int 249writectl6(int pmc) 250{ 251 if (pmc > 0 && !(ctl_shadow[pmc] & (PMCF_EN << 16))) { 252 wrmsr(msr_ctl[pmc], 0); 253 } else { 254 wrmsr(msr_ctl[pmc], ctl_shadow[pmc]); 255 } 256 return 0; 257} 258 259#define P5FLAG_P 0x200 260#define P5FLAG_E 0x100 261#define P5FLAG_USR 0x80 262#define P5FLAG_OS 0x40 263 264int 265writectl5(int pmc) 266{ 267 quad_t newval = 0; 268 269 if (ctl_shadow[1] & (PMCF_EN << 16)) { 270 if (ctl_shadow[1] & (PMCF_USR << 16)) 271 newval |= P5FLAG_USR << 16; 272 if (ctl_shadow[1] & (PMCF_OS << 16)) 273 newval |= P5FLAG_OS << 16; 274 if (!(ctl_shadow[1] & (PMCF_E << 16))) 275 newval |= P5FLAG_E << 16; 276 newval |= (ctl_shadow[1] & 0x3f) << 16; 277 } 278 if (ctl_shadow[0] & (PMCF_EN << 16)) { 279 if (ctl_shadow[0] & (PMCF_USR << 16)) 280 newval |= P5FLAG_USR; 281 if (ctl_shadow[0] & (PMCF_OS << 16)) 282 newval |= P5FLAG_OS; 283 if (!(ctl_shadow[0] & (PMCF_E << 16))) 284 newval |= P5FLAG_E; 285 newval |= ctl_shadow[0] & 0x3f; 286 } 287 288 wrmsr(msr_ctl[0], newval); 289 return 0; /* XXX should check for unimplemented bits */ 290} 291#endif /* !SMP */ 292 293/* 294 * Now the user-mode interface, called from a subdevice of mem.c. 295 */ 296static int writer; 297static int writerpmc; 298 299static int 300perfmon_open(dev_t dev, int flags, int fmt, struct thread *td) 301{ 302 if (!perfmon_cpuok) 303 return ENXIO; 304 305 if (flags & FWRITE) { 306 if (writer) { 307 return EBUSY; 308 } else { 309 writer = 1; 310 writerpmc = 0; 311 } 312 } 313 return 0; 314} 315 316static int 317perfmon_close(dev_t dev, int flags, int fmt, struct thread *td) 318{ 319 if (flags & FWRITE) { 320 int i; 321 322 for (i = 0; i < NPMC; i++) { 323 if (writerpmc & (1 << i)) 324 perfmon_fini(i); 325 } 326 writer = 0; 327 } 328 return 0; 329} 330 331static int 332perfmon_ioctl(dev_t dev, u_long cmd, caddr_t param, int flags, struct thread *td) 333{ 334 struct pmc *pmc; 335 struct pmc_data *pmcd; 336 struct pmc_tstamp *pmct; 337 int *ip; 338 int rv; 339 340 switch(cmd) { 341 case PMIOSETUP: 342 if (!(flags & FWRITE)) 343 return EPERM; 344 pmc = (struct pmc *)param; 345 346 rv = perfmon_setup(pmc->pmc_num, pmc->pmc_val); 347 if (!rv) { 348 writerpmc |= (1 << pmc->pmc_num); 349 } 350 break; 351 352 case PMIOGET: 353 pmc = (struct pmc *)param; 354 rv = perfmon_get(pmc->pmc_num, &pmc->pmc_val); 355 break; 356 357 case PMIOSTART: 358 if (!(flags & FWRITE)) 359 return EPERM; 360 361 ip = (int *)param; 362 rv = perfmon_start(*ip); 363 break; 364 365 case PMIOSTOP: 366 if (!(flags & FWRITE)) 367 return EPERM; 368 369 ip = (int *)param; 370 rv = perfmon_stop(*ip); 371 break; 372 373 case PMIORESET: 374 if (!(flags & FWRITE)) 375 return EPERM; 376 377 ip = (int *)param; 378 rv = perfmon_reset(*ip); 379 break; 380 381 case PMIOREAD: 382 pmcd = (struct pmc_data *)param; 383 rv = perfmon_read(pmcd->pmcd_num, &pmcd->pmcd_value); 384 break; 385 386 case PMIOTSTAMP: 387 if (!tsc_freq) { 388 rv = ENOTTY; 389 break; 390 } 391 pmct = (struct pmc_tstamp *)param; 392 /* XXX interface loses precision. */ 393 pmct->pmct_rate = tsc_freq / 1000000; 394 pmct->pmct_value = rdtsc(); 395 rv = 0; 396 break; 397 default: 398 rv = ENOTTY; 399 } 400 401 return rv; 402} 403