#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
#ifndef SMP
#include <machine/cputypes.h>
#endif
#include <machine/clock.h>
#include <machine/perfmon.h>
#include <machine/specialreg.h>
static int perfmon_inuse;
static int perfmon_cpuok;
#ifndef SMP
static int msr_ctl[NPMC];
#endif
static int msr_pmc[NPMC];
static unsigned int ctl_shadow[NPMC];
static quad_t pmc_shadow[NPMC];
static int (*writectl)(int);
#ifndef SMP
static int writectl5(int);
static int writectl6(int);
#endif
static d_close_t perfmon_close;
static d_open_t perfmon_open;
static d_ioctl_t perfmon_ioctl;
static void perfmon_init_dev(void *);
SYSINIT(cpu, SI_SUB_DRIVERS, SI_ORDER_ANY, perfmon_init_dev, NULL);
static struct cdevsw perfmon_cdevsw = {
.d_version = D_VERSION,
.d_flags = D_NEEDGIANT,
.d_open = perfmon_open,
.d_close = perfmon_close,
.d_ioctl = perfmon_ioctl,
.d_name = "perfmon",
};
void
perfmon_init(void)
{
#ifndef SMP
switch(cpu_class) {
case CPUCLASS_586:
perfmon_cpuok = 1;
msr_ctl[0] = MSR_P5_CESR;
msr_ctl[1] = MSR_P5_CESR;
msr_pmc[0] = MSR_P5_CTR0;
msr_pmc[1] = MSR_P5_CTR1;
writectl = writectl5;
break;
case CPUCLASS_686:
perfmon_cpuok = 1;
msr_ctl[0] = MSR_EVNTSEL0;
msr_ctl[1] = MSR_EVNTSEL1;
msr_pmc[0] = MSR_PERFCTR0;
msr_pmc[1] = MSR_PERFCTR1;
writectl = writectl6;
break;
default:
perfmon_cpuok = 0;
break;
}
#endif
}
static void
perfmon_init_dev(void *dummy)
{
make_dev(&perfmon_cdevsw, 32, UID_ROOT, GID_KMEM, 0640, "perfmon");
}
int
perfmon_avail(void)
{
return perfmon_cpuok;
}
int
perfmon_setup(int pmc, unsigned int control)
{
register_t saveintr;
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
perfmon_inuse |= (1 << pmc);
control &= ~(PMCF_SYS_FLAGS << 16);
saveintr = intr_disable();
ctl_shadow[pmc] = control;
writectl(pmc);
wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0);
intr_restore(saveintr);
return 0;
}
int
perfmon_get(int pmc, unsigned int *control)
{
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
if (perfmon_inuse & (1 << pmc)) {
*control = ctl_shadow[pmc];
return 0;
}
return EBUSY;
}
int
perfmon_fini(int pmc)
{
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
if (perfmon_inuse & (1 << pmc)) {
perfmon_stop(pmc);
ctl_shadow[pmc] = 0;
perfmon_inuse &= ~(1 << pmc);
return 0;
}
return EBUSY;
}
int
perfmon_start(int pmc)
{
register_t saveintr;
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
if (perfmon_inuse & (1 << pmc)) {
saveintr = intr_disable();
ctl_shadow[pmc] |= (PMCF_EN << 16);
wrmsr(msr_pmc[pmc], pmc_shadow[pmc]);
writectl(pmc);
intr_restore(saveintr);
return 0;
}
return EBUSY;
}
int
perfmon_stop(int pmc)
{
register_t saveintr;
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
if (perfmon_inuse & (1 << pmc)) {
saveintr = intr_disable();
pmc_shadow[pmc] = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL;
ctl_shadow[pmc] &= ~(PMCF_EN << 16);
writectl(pmc);
intr_restore(saveintr);
return 0;
}
return EBUSY;
}
int
perfmon_read(int pmc, quad_t *val)
{
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
if (perfmon_inuse & (1 << pmc)) {
if (ctl_shadow[pmc] & (PMCF_EN << 16))
*val = rdmsr(msr_pmc[pmc]) & 0xffffffffffULL;
else
*val = pmc_shadow[pmc];
return 0;
}
return EBUSY;
}
int
perfmon_reset(int pmc)
{
if (pmc < 0 || pmc >= NPMC)
return EINVAL;
if (perfmon_inuse & (1 << pmc)) {
wrmsr(msr_pmc[pmc], pmc_shadow[pmc] = 0);
return 0;
}
return EBUSY;
}
#ifndef SMP
int
writectl6(int pmc)
{
if (pmc > 0 && !(ctl_shadow[pmc] & (PMCF_EN << 16))) {
wrmsr(msr_ctl[pmc], 0);
} else {
wrmsr(msr_ctl[pmc], ctl_shadow[pmc]);
}
return 0;
}
#define P5FLAG_P 0x200
#define P5FLAG_E 0x100
#define P5FLAG_USR 0x80
#define P5FLAG_OS 0x40
int
writectl5(int pmc)
{
quad_t newval = 0;
if (ctl_shadow[1] & (PMCF_EN << 16)) {
if (ctl_shadow[1] & (PMCF_USR << 16))
newval |= P5FLAG_USR << 16;
if (ctl_shadow[1] & (PMCF_OS << 16))
newval |= P5FLAG_OS << 16;
if (!(ctl_shadow[1] & (PMCF_E << 16)))
newval |= P5FLAG_E << 16;
newval |= (ctl_shadow[1] & 0x3f) << 16;
}
if (ctl_shadow[0] & (PMCF_EN << 16)) {
if (ctl_shadow[0] & (PMCF_USR << 16))
newval |= P5FLAG_USR;
if (ctl_shadow[0] & (PMCF_OS << 16))
newval |= P5FLAG_OS;
if (!(ctl_shadow[0] & (PMCF_E << 16)))
newval |= P5FLAG_E;
newval |= ctl_shadow[0] & 0x3f;
}
wrmsr(msr_ctl[0], newval);
return 0;
}
#endif
static int writer;
static int writerpmc;
static int
perfmon_open(struct cdev *dev, int flags, int fmt, struct thread *td)
{
if (!perfmon_cpuok)
return ENXIO;
if (flags & FWRITE) {
if (writer) {
return EBUSY;
} else {
writer = 1;
writerpmc = 0;
}
}
return 0;
}
static int
perfmon_close(struct cdev *dev, int flags, int fmt, struct thread *td)
{
if (flags & FWRITE) {
int i;
for (i = 0; i < NPMC; i++) {
if (writerpmc & (1 << i))
perfmon_fini(i);
}
writer = 0;
}
return 0;
}
static int
perfmon_ioctl(struct cdev *dev, u_long cmd, caddr_t param, int flags, struct thread *td)
{
struct pmc *pmc;
struct pmc_data *pmcd;
struct pmc_tstamp *pmct;
uint64_t freq;
int *ip;
int rv;
switch(cmd) {
case PMIOSETUP:
if (!(flags & FWRITE))
return EPERM;
pmc = (struct pmc *)param;
rv = perfmon_setup(pmc->pmc_num, pmc->pmc_val);
if (!rv) {
writerpmc |= (1 << pmc->pmc_num);
}
break;
case PMIOGET:
pmc = (struct pmc *)param;
rv = perfmon_get(pmc->pmc_num, &pmc->pmc_val);
break;
case PMIOSTART:
if (!(flags & FWRITE))
return EPERM;
ip = (int *)param;
rv = perfmon_start(*ip);
break;
case PMIOSTOP:
if (!(flags & FWRITE))
return EPERM;
ip = (int *)param;
rv = perfmon_stop(*ip);
break;
case PMIORESET:
if (!(flags & FWRITE))
return EPERM;
ip = (int *)param;
rv = perfmon_reset(*ip);
break;
case PMIOREAD:
pmcd = (struct pmc_data *)param;
rv = perfmon_read(pmcd->pmcd_num, &pmcd->pmcd_value);
break;
case PMIOTSTAMP:
freq = atomic_load_acq_64(&tsc_freq);
if (freq == 0) {
rv = ENOTTY;
break;
}
pmct = (struct pmc_tstamp *)param;
pmct->pmct_rate = freq / 1000000;
pmct->pmct_value = rdtsc();
rv = 0;
break;
default:
rv = ENOTTY;
}
return rv;
}