#include <sys/param.h>
#include <sys/conf.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/cpuhelper.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <machine/specialreg.h>
#include <machine/cpufunc.h>
#include <machine/cputypes.h>
#include <machine/md_var.h>
#define INTEL_MSR_PERF_BIAS 0x1b0
#define INTEL_MSR_PERF_BIAS_HINTMASK 0xf
struct perfbias_softc {
device_t sc_dev;
int sc_cpuid;
int sc_hint;
struct sysctl_ctx_list sc_sysctl_ctx;
struct sysctl_oid *sc_sysctl_tree;
};
static void perfbias_identify(driver_t *, device_t);
static int perfbias_probe(device_t);
static int perfbias_attach(device_t);
static int perfbias_detach(device_t);
static void perfbias_set_handler(struct cpuhelper_msg *);
static int perfbias_set(struct perfbias_softc *, int);
static int perfbias_sysctl(SYSCTL_HANDLER_ARGS);
static device_method_t perfbias_methods[] = {
DEVMETHOD(device_identify, perfbias_identify),
DEVMETHOD(device_probe, perfbias_probe),
DEVMETHOD(device_attach, perfbias_attach),
DEVMETHOD(device_detach, perfbias_detach),
DEVMETHOD_END
};
static driver_t perfbias_driver = {
"perfbias",
perfbias_methods,
sizeof(struct perfbias_softc),
};
static devclass_t perfbias_devclass;
DRIVER_MODULE(perfbias, cpu, perfbias_driver, perfbias_devclass, NULL, NULL);
static void
perfbias_identify(driver_t *driver, device_t parent)
{
device_t child;
u_int regs[4];
if (device_find_child(parent, "perfbias", -1) != NULL)
return;
if (cpu_high < 6 || cpu_vendor_id != CPU_VENDOR_INTEL)
return;
do_cpuid(6, regs);
if ((regs[2] & CPUID_THERMAL2_SETBH) == 0)
return;
child = device_add_child(parent, "perfbias", device_get_unit(parent));
if (child == NULL)
device_printf(parent, "add perfbias failed\n");
}
static int
perfbias_probe(device_t dev)
{
device_set_desc(dev, "CPU perf-energy bias");
return 0;
}
static int
perfbias_attach(device_t dev)
{
struct perfbias_softc *sc = device_get_softc(dev);
sc->sc_dev = dev;
sc->sc_cpuid = device_get_unit(dev);
sysctl_ctx_init(&sc->sc_sysctl_ctx);
sc->sc_sysctl_tree = SYSCTL_ADD_NODE(&sc->sc_sysctl_ctx,
SYSCTL_STATIC_CHILDREN(_machdep), OID_AUTO,
device_get_nameunit(sc->sc_dev), CTLFLAG_RD, 0, "");
if (sc->sc_sysctl_tree == NULL) {
device_printf(sc->sc_dev, "can't add sysctl node\n");
return ENOMEM;
}
SYSCTL_ADD_PROC(&sc->sc_sysctl_ctx,
SYSCTL_CHILDREN(sc->sc_sysctl_tree),
OID_AUTO, "hint", CTLTYPE_INT | CTLFLAG_RW, sc, 0,
perfbias_sysctl, "I", "0 - highest perf; 15 - max energy saving");
return 0;
}
static int
perfbias_detach(device_t dev)
{
struct perfbias_softc *sc = device_get_softc(dev);
if (sc->sc_sysctl_tree != NULL)
sysctl_ctx_free(&sc->sc_sysctl_ctx);
perfbias_set(sc, 0);
return 0;
}
static int
perfbias_sysctl(SYSCTL_HANDLER_ARGS)
{
struct perfbias_softc *sc = (void *)arg1;
int error, hint;
hint = sc->sc_hint;
error = sysctl_handle_int(oidp, &hint, 0, req);
if (error || req->newptr == NULL)
return error;
if (hint < 0 || hint > INTEL_MSR_PERF_BIAS_HINTMASK)
return EINVAL;
return perfbias_set(sc, hint);
}
static void
perfbias_set_handler(struct cpuhelper_msg *msg)
{
struct perfbias_softc *sc = msg->ch_cbarg;
uint64_t hint = msg->ch_cbarg1;
wrmsr(INTEL_MSR_PERF_BIAS, hint);
hint = rdmsr(INTEL_MSR_PERF_BIAS);
sc->sc_hint = hint & INTEL_MSR_PERF_BIAS_HINTMASK;
cpuhelper_replymsg(msg, 0);
}
static int
perfbias_set(struct perfbias_softc *sc, int hint)
{
struct cpuhelper_msg msg;
cpuhelper_initmsg(&msg, &curthread->td_msgport,
perfbias_set_handler, sc, MSGF_PRIORITY);
msg.ch_cbarg1 = hint;
return (cpuhelper_domsg(&msg, sc->sc_cpuid));
}