#include <sys/types.h>
#include <sys/param.h>
#include <sys/sbuf.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/smp.h>
#include <sys/proc.h>
#include <sys/sched.h>
#include <machine/cpu.h>
#include <machine/md_var.h>
#include <machine/cputypes.h>
#include <machine/specialreg.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/acpica/acpivar.h>
#include <x86/cpufreq/hwpstate_common.h>
#include <x86/cpufreq/hwpstate_intel_internal.h>
#include "acpi_if.h"
#include "cpufreq_if.h"
extern uint64_t tsc_freq;
static int intel_hwpstate_probe(device_t dev);
static int intel_hwpstate_attach(device_t dev);
static int intel_hwpstate_detach(device_t dev);
static int intel_hwpstate_suspend(device_t dev);
static int intel_hwpstate_resume(device_t dev);
static int intel_hwpstate_get(device_t dev, struct cf_setting *cf);
static int intel_hwpstate_type(device_t dev, int *type);
static device_method_t intel_hwpstate_methods[] = {
DEVMETHOD(device_identify, intel_hwpstate_identify),
DEVMETHOD(device_probe, intel_hwpstate_probe),
DEVMETHOD(device_attach, intel_hwpstate_attach),
DEVMETHOD(device_detach, intel_hwpstate_detach),
DEVMETHOD(device_suspend, intel_hwpstate_suspend),
DEVMETHOD(device_resume, intel_hwpstate_resume),
DEVMETHOD(cpufreq_drv_get, intel_hwpstate_get),
DEVMETHOD(cpufreq_drv_type, intel_hwpstate_type),
DEVMETHOD_END
};
#define RDMSR_ON_CPU(sc, msr, val) \
(x86_msr_op(msr, \
MSR_OP_RENDEZVOUS_ONE | MSR_OP_READ | \
MSR_OP_CPUID(sc->cpuid), \
0, val));
#define WRMSR_ON_CPU(sc, msr, val) \
x86_msr_op(msr, \
MSR_OP_RENDEZVOUS_ONE | MSR_OP_WRITE | \
MSR_OP_CPUID(sc->cpuid), \
val, NULL)
struct hwp_softc {
device_t dev;
u_int cpuid;
bool hwp_notifications;
bool hwp_activity_window;
bool hwp_pref_ctrl;
bool hwp_pkg_ctrl;
bool hwp_pkg_ctrl_en;
bool hwp_perf_bias;
bool hwp_perf_bias_cached;
uint64_t req;
uint64_t hwp_energy_perf_bias;
uint8_t high;
uint8_t guaranteed;
uint8_t efficient;
uint8_t low;
};
static driver_t hwpstate_intel_driver = {
"hwpstate_intel",
intel_hwpstate_methods,
sizeof(struct hwp_softc),
};
DRIVER_MODULE(hwpstate_intel, cpu, hwpstate_intel_driver, NULL, NULL);
MODULE_VERSION(hwpstate_intel, 1);
#define HWP_ERROR_CPPC_ENABLE (1 << 0)
#define HWP_ERROR_CPPC_CAPS (1 << 1)
#define HWP_ERROR_CPPC_REQUEST (1 << 2)
#define HWP_ERROR_CPPC_REQUEST_WRITE (1 << 3)
#define HWP_ERROR_CPPC_REQUEST_PKG (1 << 4)
#define HWP_ERROR_CPPC_EPP_WRITE (1 << 5)
static inline bool
hwp_has_error(u_int res, u_int err)
{
return ((res & err) != 0);
}
struct get_cppc_regs_data {
const struct hwp_softc *sc;
uint64_t enabled;
uint64_t caps;
uint64_t request;
uint64_t request_pkg;
u_int res;
};
static void
get_cppc_regs_cb(void *args)
{
struct get_cppc_regs_data *const data = args;
int error;
data->res = 0;
error = rdmsr_safe(MSR_IA32_PM_ENABLE, &data->enabled);
if (error != 0)
data->res |= HWP_ERROR_CPPC_ENABLE;
error = rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &data->caps);
if (error != 0)
data->res |= HWP_ERROR_CPPC_CAPS;
error = rdmsr_safe(MSR_IA32_HWP_REQUEST, &data->request);
if (error != 0)
data->res |= HWP_ERROR_CPPC_REQUEST;
if (data->sc->hwp_pkg_ctrl) {
error = rdmsr_safe(MSR_IA32_HWP_REQUEST_PKG,
&data->request_pkg);
if (error != 0)
data->res |= HWP_ERROR_CPPC_REQUEST_PKG;
}
}
static inline void
get_cppc_regs_one(const struct hwp_softc *const sc,
struct get_cppc_regs_data *const req)
{
req->sc = sc;
smp_rendezvous_cpu(sc->cpuid,
smp_no_rendezvous_barrier, get_cppc_regs_cb,
smp_no_rendezvous_barrier, req);
}
#define MSR_NOT_READ_MSG "Not read (fault or previous errors)"
static int
intel_hwp_dump_sysctl_handler(SYSCTL_HANDLER_ARGS)
{
const struct hwp_softc *const sc = arg1;
const u_int cpuid = sc->cpuid;
struct sbuf *sb;
struct get_cppc_regs_data data;
int ret = 0;
get_cppc_regs_one(sc, &data);
sb = sbuf_new(NULL, NULL, 1024, SBUF_FIXEDLEN | SBUF_INCLUDENUL);
sbuf_putc(sb, '\n');
if (hwp_has_error(data.res, HWP_ERROR_CPPC_ENABLE))
sbuf_printf(sb, "CPU%u: IA32_PM_ENABLE: " MSR_NOT_READ_MSG "\n",
cpuid);
else
sbuf_printf(sb, "CPU%d: HWP %sabled\n", cpuid,
((data.enabled & 1) ? "En" : "Dis"));
if (data.enabled == 0)
goto out;
if (hwp_has_error(data.res, HWP_ERROR_CPPC_CAPS)) {
sbuf_printf(sb,
"IA32_HWP_CAPABILITIES: " MSR_NOT_READ_MSG "\n");
} else {
sbuf_printf(sb, "\tHighest Performance: %03ju\n",
data.caps & 0xff);
sbuf_printf(sb, "\tGuaranteed Performance: %03ju\n",
(data.caps >> 8) & 0xff);
sbuf_printf(sb, "\tEfficient Performance: %03ju\n",
(data.caps >> 16) & 0xff);
sbuf_printf(sb, "\tLowest Performance: %03ju\n",
(data.caps >> 24) & 0xff);
sbuf_putc(sb, '\n');
}
#define pkg_print(x, name, offset) do { \
if (!sc->hwp_pkg_ctrl || (data.request & x) != 0) \
sbuf_printf(sb, "\t%s: %03u\n", name, \
(unsigned)(data.request >> offset) & 0xff); \
else \
sbuf_printf(sb, "\t%s: %03u\n", name, \
(unsigned)(data.request_pkg >> offset) & 0xff); \
} while (0)
if (hwp_has_error(data.res, HWP_ERROR_CPPC_REQUEST)) {
sbuf_printf(sb, "IA32_HWP_REQUEST: " MSR_NOT_READ_MSG "\n");
} else if (hwp_has_error(data.res, HWP_ERROR_CPPC_REQUEST_PKG)) {
sbuf_printf(sb, "IA32_HWP_REQUEST_PKG: " MSR_NOT_READ_MSG "\n");
} else {
pkg_print(IA32_HWP_REQUEST_EPP_VALID,
"Requested Efficiency Performance Preference", 24);
pkg_print(IA32_HWP_REQUEST_DESIRED_VALID,
"Requested Desired Performance", 16);
pkg_print(IA32_HWP_REQUEST_MAXIMUM_VALID,
"Requested Maximum Performance", 8);
pkg_print(IA32_HWP_REQUEST_MINIMUM_VALID,
"Requested Minimum Performance", 0);
}
#undef pkg_print
sbuf_putc(sb, '\n');
out:
ret = sbuf_finish(sb);
if (ret == 0)
ret = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb));
sbuf_delete(sb);
return (ret);
}
#define EPB_TO_EPP(x) ((x) * 17)
#define EPP_TO_EPB(x) ((x) >> 4)
static int
sysctl_epp_select(SYSCTL_HANDLER_ARGS)
{
const device_t dev = arg1;
struct hwp_softc *const sc = device_get_softc(dev);
uint64_t epb;
uint32_t val;
int ret;
if (!sc->hwp_pref_ctrl && !sc->hwp_perf_bias)
return (ENODEV);
if (sc->hwp_pref_ctrl) {
val = (sc->req & IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) >> 24;
} else {
if (!sc->hwp_perf_bias_cached) {
ret = RDMSR_ON_CPU(sc, MSR_IA32_ENERGY_PERF_BIAS, &epb);
if (ret)
goto out;
sc->hwp_energy_perf_bias = epb;
sc->hwp_perf_bias_cached = true;
}
val = sc->hwp_energy_perf_bias &
IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK;
val = EPB_TO_EPP(val);
}
MPASS(val >= 0 && val <= 255);
ret = sysctl_handle_int(oidp, &val, 0, req);
if (ret || req->newptr == NULL)
goto out;
if (val > 255 || val < 0) {
ret = EINVAL;
goto out;
}
if (sc->hwp_pref_ctrl) {
const uint64_t req_cached = sc->req = (sc->req &
~IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) |
(val << 24u);
if (sc->hwp_pkg_ctrl_en)
ret = WRMSR_ON_CPU(sc, MSR_IA32_HWP_REQUEST_PKG,
req_cached);
else
ret = WRMSR_ON_CPU(sc, MSR_IA32_HWP_REQUEST,
req_cached);
} else {
val = EPP_TO_EPB(val);
MPASS((val & ~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) == 0);
sc->hwp_energy_perf_bias =
((sc->hwp_energy_perf_bias &
~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) | val);
ret = WRMSR_ON_CPU(sc, MSR_IA32_ENERGY_PERF_BIAS,
sc->hwp_energy_perf_bias);
}
out:
return (ret);
}
void
intel_hwpstate_identify(driver_t *driver, device_t parent)
{
int i;
if (device_find_child(parent, "hwpstate_intel", DEVICE_UNIT_ANY) != NULL)
return;
if (cpu_vendor_id != CPU_VENDOR_INTEL)
return;
if (resource_disabled("hwpstate_intel", 0))
return;
if ((cpu_power_eax & CPUTPM1_HWP) == 0)
return;
CPU_FOREACH(i) {
if (pcpu_find(i)->pc_small_core) {
hwpstate_pkg_ctrl_enable = false;
break;
}
}
if (BUS_ADD_CHILD(parent, 10, "hwpstate_intel", device_get_unit(parent))
== NULL)
device_printf(parent, "hwpstate_intel: add child failed\n");
}
static int
intel_hwpstate_probe(device_t dev)
{
device_set_desc(dev, "Intel Speed Shift");
return (BUS_PROBE_NOWILDCARD);
}
struct set_autonomous_hwp_cb {
struct hwp_softc *sc;
uint32_t flag;
};
static void
set_autonomous_hwp_cb(void *arg)
{
int ret;
uint64_t caps, req;
struct set_autonomous_hwp_cb *data = arg;
struct hwp_softc *sc = data->sc;
data->flag = 0;
ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1);
if (ret) {
data->flag |= HWP_ERROR_CPPC_ENABLE;
return;
}
ret = rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &caps);
if (ret) {
data->flag |= HWP_ERROR_CPPC_CAPS;
}
ret = rdmsr_safe(MSR_IA32_HWP_REQUEST, &req);
if (ret) {
data->flag |= HWP_ERROR_CPPC_REQUEST;
}
sc->high = IA32_HWP_CAPABILITIES_HIGHEST_PERFORMANCE(caps);
sc->guaranteed = IA32_HWP_CAPABILITIES_GUARANTEED_PERFORMANCE(caps);
sc->efficient = IA32_HWP_CAPABILITIES_EFFICIENT_PERFORMANCE(caps);
sc->low = IA32_HWP_CAPABILITIES_LOWEST_PERFORMANCE(caps);
req &= ~IA32_HWP_DESIRED_PERFORMANCE;
req &= ~IA32_HWP_ACTIVITY_WINDOW;
req &= ~IA32_HWP_MINIMUM_PERFORMANCE;
req |= sc->low;
req &= ~IA32_HWP_REQUEST_MAXIMUM_PERFORMANCE;
req |= sc->high << 8;
sc->req = req;
if (sc->hwp_pkg_ctrl_en)
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST,
sc->req | IA32_HWP_REQUEST_PACKAGE_CONTROL);
else
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req);
if (ret)
data->flag |= HWP_ERROR_CPPC_REQUEST_WRITE;
if (sc->hwp_pkg_ctrl_en) {
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
if (ret)
data->flag |= HWP_ERROR_CPPC_REQUEST_PKG;
}
}
static inline void
set_autonomous_hwp_send_one(struct hwp_softc *sc,
struct set_autonomous_hwp_cb *data)
{
data->sc = sc;
smp_rendezvous_cpu(sc->cpuid, smp_no_rendezvous_barrier,
set_autonomous_hwp_cb, smp_no_rendezvous_barrier, data);
}
static int
set_autonomous_hwp(struct hwp_softc *const sc)
{
const device_t dev = sc->dev;
const u_int cpuid = sc->cpuid;
struct set_autonomous_hwp_cb data;
set_autonomous_hwp_send_one(sc, &data);
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_ENABLE)) {
device_printf(dev, "Failed to enable HWP for cpu%d (%d)\n",
cpuid, EFAULT);
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_REQUEST)) {
device_printf(dev,
"Failed to read HWP request MSR for cpu%d (%d)\n",
cpuid, EFAULT);
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_CAPS)) {
device_printf(dev,
"Failed to read HWP capabilities MSR for cpu%d (%d)\n",
cpuid, EFAULT);
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_REQUEST_WRITE)) {
device_printf(dev,
"Failed to setup%s autonomous HWP for cpu%d\n",
sc->hwp_pkg_ctrl_en ? " PKG" : "", cpuid);
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_REQUEST_PKG))
device_printf(dev,
"Failed to set autonomous HWP for package\n");
out:
if (data.flag)
return (EFAULT);
return (0);
}
static int
intel_hwpstate_attach(device_t dev)
{
struct hwp_softc *sc;
sc = device_get_softc(dev);
sc->dev = dev;
if (cpu_get_pcpu(dev) == NULL) {
device_printf(dev,
"Parent bus does not provide a per-CPU structure!");
return (ENXIO);
}
sc->cpuid = cpu_get_pcpuid(dev);
if (cpu_power_eax & CPUTPM1_HWP_NOTIFICATION)
sc->hwp_notifications = true;
if (cpu_power_eax & CPUTPM1_HWP_ACTIVITY_WINDOW)
sc->hwp_activity_window = true;
if (cpu_power_eax & CPUTPM1_HWP_PERF_PREF)
sc->hwp_pref_ctrl = true;
if (cpu_power_eax & CPUTPM1_HWP_PKG)
sc->hwp_pkg_ctrl = true;
sc->hwp_pkg_ctrl_en = (sc->hwp_pkg_ctrl && hwpstate_pkg_ctrl_enable);
if (cpu_power_ecx & CPUID_PERF_BIAS)
sc->hwp_perf_bias = true;
set_autonomous_hwp(sc);
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
SYSCTL_STATIC_CHILDREN(_debug), OID_AUTO, device_get_nameunit(dev),
CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_MPSAFE,
sc, 0, intel_hwp_dump_sysctl_handler, "A", "");
SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO,
"epp", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, dev, 0,
sysctl_epp_select, "I",
"Efficiency/Performance Preference "
"(range from 0, most performant, through 100, most efficient)");
return (cpufreq_register(dev));
}
static int
intel_hwpstate_detach(device_t dev)
{
return (cpufreq_unregister(dev));
}
static int
intel_hwpstate_get(device_t dev, struct cf_setting *set)
{
const struct hwp_softc *const sc = device_get_softc(dev);
uint64_t rate;
int ret;
if (set == NULL)
return (EINVAL);
memset(set, CPUFREQ_VAL_UNKNOWN, sizeof(*set));
set->dev = dev;
ret = cpu_est_clockrate(sc->cpuid, &rate);
if (ret == 0)
set->freq = rate / 1000000;
set->volts = CPUFREQ_VAL_UNKNOWN;
set->power = CPUFREQ_VAL_UNKNOWN;
set->lat = CPUFREQ_VAL_UNKNOWN;
return (0);
}
static int
intel_hwpstate_type(device_t dev, int *type)
{
if (type == NULL)
return (EINVAL);
*type = CPUFREQ_TYPE_ABSOLUTE | CPUFREQ_FLAG_INFO_ONLY | CPUFREQ_FLAG_UNCACHED;
return (0);
}
static int
intel_hwpstate_suspend(device_t dev)
{
return (0);
}
struct hwpstate_resume_cb {
const struct hwp_softc *sc;
uint32_t flag;
};
static void
hwpstate_resume_cb(void *arg)
{
struct hwpstate_resume_cb *const data = arg;
const struct hwp_softc *const sc = data->sc;
int ret;
data->flag = 0;
ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1);
if (ret) {
data->flag |= HWP_ERROR_CPPC_ENABLE;
return;
}
if (sc->hwp_pkg_ctrl_en)
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST,
sc->req | IA32_HWP_REQUEST_PACKAGE_CONTROL);
else
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req);
if (ret) {
data->flag |= HWP_ERROR_CPPC_REQUEST_WRITE;
return;
}
if (sc->hwp_pkg_ctrl_en) {
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
if (ret) {
data->flag |= HWP_ERROR_CPPC_REQUEST_PKG;
return;
}
}
if (!sc->hwp_pref_ctrl && sc->hwp_perf_bias_cached) {
ret = wrmsr_safe(MSR_IA32_ENERGY_PERF_BIAS,
sc->hwp_energy_perf_bias);
if (ret) {
data->flag |= HWP_ERROR_CPPC_EPP_WRITE;
}
}
}
static inline void
hwpstate_resume_send_one(const struct hwp_softc *sc, struct hwpstate_resume_cb *req)
{
req->sc = sc;
smp_rendezvous_cpu(sc->cpuid, smp_no_rendezvous_barrier,
hwpstate_resume_cb, smp_no_rendezvous_barrier, req);
}
static int
intel_hwpstate_resume(device_t dev)
{
const struct hwp_softc *const sc = device_get_softc(dev);
const u_int cpuid = sc->cpuid;
struct hwpstate_resume_cb data;
hwpstate_resume_send_one(sc, &data);
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_ENABLE)) {
device_printf(dev,
"Failed to enable HWP for cpu%d after suspend (%d)\n",
cpuid, EFAULT);
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_REQUEST_WRITE)) {
device_printf(dev,
"Failed to set%s autonomous HWP for cpu%d after suspend\n",
sc->hwp_pkg_ctrl_en ? " PKG" : "", cpuid);
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_REQUEST_PKG)) {
device_printf(dev,
"Failed to set autonomous HWP for package after "
"suspend\n");
goto out;
}
if (hwp_has_error(data.flag, HWP_ERROR_CPPC_EPP_WRITE)) {
device_printf(dev,
"Failed to set energy perf bias for cpu%d after "
"suspend\n",
cpuid);
}
out:
if (data.flag)
return (EFAULT);
return (0);
}