hwpstate_intel: Use ipi instead of thread_lock + sched_bind

Reviewed by:    olce
Event:          Halifax Hackathon 202606
Sponsored by:   The FreeBSD Foundation
Differential Revision: https://reviews.freebsd.org/D55628
This commit is contained in:
ShengYi Hung committed 2026-06-24 22:30:25 +08:00
1 parent 0976744085
commit 29b8220b17
1 file changed
+278 -151
+278 -151
View File
@@ -2,6 +2,9 @@
* SPDX-License-Identifier: BSD-2-Clause
*
* Copyright (c) 2018 Intel Corporation
* Copyright (c) 2026 The FreeBSD Foundation
* Portions of this software were developed by ShengYi Hung
* <aokblast@FreeBSD.org> under sponsorship from the FreeBSD Foundation.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted providing that the following conditions
@@ -81,6 +84,18 @@ static device_method_t intel_hwpstate_methods[] = {
DEVMETHOD_END
};
#define RDMSR_ON_CPU(dev, msr, val) \
(x86_msr_op(msr, \
MSR_OP_RENDEZVOUS_ONE | MSR_OP_READ | \
MSR_OP_CPUID(cpu_get_pcpu(dev)->pc_cpuid), \
0, val));
#define WRMSR_ON_CPU(dev, msr, val) \
x86_msr_op(msr, \
MSR_OP_RENDEZVOUS_ONE | MSR_OP_WRITE | \
MSR_OP_CPUID(cpu_get_pcpu(dev)->pc_cpuid), \
val, NULL)
struct hwp_softc {
device_t dev;
bool hwp_notifications;
@@ -109,6 +124,55 @@ static driver_t hwpstate_intel_driver = {
DRIVER_MODULE(hwpstate_intel, cpu, hwpstate_intel_driver, NULL, NULL);
MODULE_VERSION(hwpstate_intel, 1);
/*
* Internal errors conveyed by code executing on another CPU.
*/
#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 << 3)
#define HWP_ERROR_CPPC_EPP_WRITE (1 << 4)
struct dump_cppc_request_cb {
struct hwp_softc *sc;
uint64_t enabled;
uint64_t caps;
uint64_t request;
uint64_t request_pkg;
int err;
};
static void
dump_cppc_request_cb(void *args)
{
struct dump_cppc_request_cb *const data = args;
if (rdmsr_safe(MSR_IA32_PM_ENABLE, &data->enabled))
data->err |= HWP_ERROR_CPPC_ENABLE;
if (rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &data->caps))
data->err |= HWP_ERROR_CPPC_CAPS;
if (rdmsr_safe(MSR_IA32_HWP_REQUEST, &data->request))
data->err |= HWP_ERROR_CPPC_REQUEST;
if (data->sc->hwp_pkg_ctrl &&
(data->request & IA32_HWP_REQUEST_PACKAGE_CONTROL)) {
if (rdmsr_safe(MSR_IA32_HWP_REQUEST_PKG, &data->request_pkg))
data->err |= HWP_ERROR_CPPC_REQUEST_PKG;
}
}
static inline void
dump_cppc_request_one(struct hwp_softc *sc, struct dump_cppc_request_cb *req)
{
req->sc = sc;
smp_rendezvous_cpu(cpu_get_pcpu(sc->dev)->pc_cpuid,
smp_no_rendezvous_barrier, dump_cppc_request_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)
{
@@ -116,8 +180,8 @@ intel_hwp_dump_sysctl_handler(SYSCTL_HANDLER_ARGS)
struct pcpu *pc;
struct sbuf *sb;
struct hwp_softc *sc;
uint64_t data, data2;
int ret;
struct dump_cppc_request_cb data;
int ret = 0;
sc = (struct hwp_softc *)arg1;
dev = sc->dev;
@@ -126,59 +190,61 @@ intel_hwp_dump_sysctl_handler(SYSCTL_HANDLER_ARGS)
if (pc == NULL)
return (ENXIO);
dump_cppc_request_one(sc, &data);
sb = sbuf_new(NULL, NULL, 1024, SBUF_FIXEDLEN | SBUF_INCLUDENUL);
sbuf_putc(sb, '\n');
thread_lock(curthread);
sched_bind(curthread, pc->pc_cpuid);
thread_unlock(curthread);
rdmsr_safe(MSR_IA32_PM_ENABLE, &data);
sbuf_printf(sb, "CPU%d: HWP %sabled\n", pc->pc_cpuid,
((data & 1) ? "En" : "Dis"));
if (data.err | HWP_ERROR_CPPC_ENABLE)
sbuf_printf(sb, "CPU%u: IA32_PM_ENABLE: " MSR_NOT_READ_MSG "\n",
pc->pc_cpuid);
else
sbuf_printf(sb, "CPU%d: HWP %sabled\n", pc->pc_cpuid,
((data.enabled & 1) ? "En" : "Dis"));
if (data == 0) {
ret = 0;
if (data.enabled == 0)
goto out;
if (data.err | 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');
}
rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &data);
sbuf_printf(sb, "\tHighest Performance: %03ju\n", data & 0xff);
sbuf_printf(sb, "\tGuaranteed Performance: %03ju\n", (data >> 8) & 0xff);
sbuf_printf(sb, "\tEfficient Performance: %03ju\n", (data >> 16) & 0xff);
sbuf_printf(sb, "\tLowest Performance: %03ju\n", (data >> 24) & 0xff);
rdmsr_safe(MSR_IA32_HWP_REQUEST, &data);
data2 = 0;
if (sc->hwp_pkg_ctrl && (data & IA32_HWP_REQUEST_PACKAGE_CONTROL))
rdmsr_safe(MSR_IA32_HWP_REQUEST_PKG, &data2);
sbuf_putc(sb, '\n');
#define pkg_print(x, name, offset) do { \
if (!sc->hwp_pkg_ctrl || (data & x) != 0) \
if (!sc->hwp_pkg_ctrl || (data.request & x) != 0) \
sbuf_printf(sb, "\t%s: %03u\n", name, \
(unsigned)(data >> offset) & 0xff); \
(unsigned)(data.request >> offset) & 0xff); \
else \
sbuf_printf(sb, "\t%s: %03u\n", name, \
(unsigned)(data2 >> offset) & 0xff); \
(unsigned)(data.request_pkg >> offset) & 0xff); \
} while (0)
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);
if (data.err | HWP_ERROR_CPPC_REQUEST) {
sbuf_printf(sb, "IA32_HWP_REQUEST: " MSR_NOT_READ_MSG "\n");
} else if (data.err | 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:
thread_lock(curthread);
sched_unbind(curthread);
thread_unlock(curthread);
ret = sbuf_finish(sb);
if (ret == 0)
@@ -244,6 +310,7 @@ sysctl_epp_select(SYSCTL_HANDLER_ARGS)
struct pcpu *pc;
uint64_t epb;
uint32_t val;
uint64_t req_cached;
int ret;
dev = oidp->oid_arg1;
@@ -255,10 +322,6 @@ sysctl_epp_select(SYSCTL_HANDLER_ARGS)
if (pc == NULL)
return (ENXIO);
thread_lock(curthread);
sched_bind(curthread, pc->pc_cpuid);
thread_unlock(curthread);
if (sc->hwp_pref_ctrl) {
val = (sc->req & IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) >> 24;
val = raw_to_percent(val);
@@ -269,7 +332,8 @@ sysctl_epp_select(SYSCTL_HANDLER_ARGS)
* This register is per-core (but not HT).
*/
if (!sc->hwp_perf_bias_cached) {
ret = rdmsr_safe(MSR_IA32_ENERGY_PERF_BIAS, &epb);
ret = RDMSR_ON_CPU(dev, MSR_IA32_ENERGY_PERF_BIAS,
&epb);
if (ret)
goto out;
sc->hwp_energy_perf_bias = epb;
@@ -293,15 +357,17 @@ sysctl_epp_select(SYSCTL_HANDLER_ARGS)
if (sc->hwp_pref_ctrl) {
val = percent_to_raw(val);
sc->req =
((sc->req & ~IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE)
| (val << 24u));
req_cached = sc->req =
((sc->req &
~IA32_HWP_REQUEST_ENERGY_PERFORMANCE_PREFERENCE) |
(val << 24u));
if (sc->hwp_pkg_ctrl_en)
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
ret = WRMSR_ON_CPU(dev, MSR_IA32_HWP_REQUEST_PKG,
req_cached);
else
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req);
ret = WRMSR_ON_CPU(dev, MSR_IA32_HWP_REQUEST,
req_cached);
} else {
val = percent_to_raw_perf_bias(val);
MPASS((val & ~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) == 0);
@@ -309,15 +375,11 @@ sysctl_epp_select(SYSCTL_HANDLER_ARGS)
sc->hwp_energy_perf_bias =
((sc->hwp_energy_perf_bias &
~IA32_ENERGY_PERF_BIAS_POLICY_HINT_MASK) | val);
ret = wrmsr_safe(MSR_IA32_ENERGY_PERF_BIAS,
ret = WRMSR_ON_CPU(dev, MSR_IA32_ENERGY_PERF_BIAS,
sc->hwp_energy_perf_bias);
}
out:
thread_lock(curthread);
sched_unbind(curthread);
thread_unlock(curthread);
return (ret);
}
@@ -377,23 +439,20 @@ intel_hwpstate_probe(device_t dev)
return (BUS_PROBE_NOWILDCARD);
}
static int
set_autonomous_hwp(struct hwp_softc *sc)
struct set_autonomous_hwp_cb {
struct hwp_softc *sc;
uint32_t flag;
};
static void
set_autonomous_hwp_cb(void *arg)
{
struct pcpu *pc;
device_t dev;
uint64_t caps;
int ret;
uint64_t caps, req;
struct set_autonomous_hwp_cb *data = arg;
struct hwp_softc *sc = data->sc;
dev = sc->dev;
pc = cpu_get_pcpu(dev);
if (pc == NULL)
return (ENXIO);
thread_lock(curthread);
sched_bind(curthread, pc->pc_cpuid);
thread_unlock(curthread);
data->flag = 0;
/* XXX: Many MSRs aren't readable until feature is enabled */
ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1);
@@ -406,27 +465,17 @@ set_autonomous_hwp(struct hwp_softc *sc)
* condition should not happen given we gate on the HWP CPUID
* feature flag, if the Intel SDM is correct.
*/
device_printf(dev, "Failed to enable HWP for cpu%d (%d)\n",
pc->pc_cpuid, ret);
goto out;
data->flag |= HWP_ERROR_CPPC_ENABLE;
return;
}
ret = rdmsr_safe(MSR_IA32_HWP_REQUEST, &sc->req);
if (ret) {
device_printf(dev,
"Failed to read HWP request MSR for cpu%d (%d)\n",
pc->pc_cpuid, ret);
goto out;
}
ret = rdmsr_safe(MSR_IA32_HWP_CAPABILITIES, &caps);
if (ret) {
device_printf(dev,
"Failed to read HWP capabilities MSR for cpu%d (%d)\n",
pc->pc_cpuid, ret);
goto out;
data->flag |= HWP_ERROR_CPPC_CAPS;
}
ret = rdmsr_safe(MSR_IA32_HWP_REQUEST, &req);
if (ret) {
data->flag |= HWP_ERROR_CPPC_REQUEST;
}
/*
* High and low are static; "guaranteed" is dynamic; and efficient is
* also dynamic.
@@ -437,31 +486,30 @@ set_autonomous_hwp(struct hwp_softc *sc)
sc->low = IA32_HWP_CAPABILITIES_LOWEST_PERFORMANCE(caps);
/* hardware autonomous selection determines the performance target */
sc->req &= ~IA32_HWP_DESIRED_PERFORMANCE;
req &= ~IA32_HWP_DESIRED_PERFORMANCE;
/* enable HW dynamic selection of window size */
sc->req &= ~IA32_HWP_ACTIVITY_WINDOW;
req &= ~IA32_HWP_ACTIVITY_WINDOW;
/* IA32_HWP_REQUEST.Minimum_Performance = IA32_HWP_CAPABILITIES.Lowest_Performance */
sc->req &= ~IA32_HWP_MINIMUM_PERFORMANCE;
sc->req |= sc->low;
/* IA32_HWP_REQUEST.Minimum_Performance =
* IA32_HWP_CAPABILITIES.Lowest_Performance */
req &= ~IA32_HWP_MINIMUM_PERFORMANCE;
req |= sc->low;
/* IA32_HWP_REQUEST.Maximum_Performance = IA32_HWP_CAPABILITIES.Highest_Performance. */
sc->req &= ~IA32_HWP_REQUEST_MAXIMUM_PERFORMANCE;
sc->req |= sc->high << 8;
/* IA32_HWP_REQUEST.Maximum_Performance =
* IA32_HWP_CAPABILITIES.Highest_Performance. */
req &= ~IA32_HWP_REQUEST_MAXIMUM_PERFORMANCE;
req |= sc->high << 8;
sc->req = req;
/* If supported, request package-level control for this CPU. */
if (sc->hwp_pkg_ctrl_en)
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST, sc->req |
IA32_HWP_REQUEST_PACKAGE_CONTROL);
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) {
device_printf(dev,
"Failed to setup%s autonomous HWP for cpu%d\n",
sc->hwp_pkg_ctrl_en ? " PKG" : "", pc->pc_cpuid);
goto out;
}
if (ret)
data->flag |= HWP_ERROR_CPPC_REQUEST_WRITE;
/* If supported, write the PKG-wide control MSR. */
if (sc->hwp_pkg_ctrl_en) {
@@ -472,25 +520,72 @@ set_autonomous_hwp(struct hwp_softc *sc)
* not exist." (Intel SDM §14.4.4)
*/
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
if (ret) {
device_printf(dev,
"Failed to set autonomous HWP for package\n");
}
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(cpu_get_pcpu(sc->dev)->pc_cpuid,
smp_no_rendezvous_barrier, set_autonomous_hwp_cb,
smp_no_rendezvous_barrier, data);
}
static int
set_autonomous_hwp(struct hwp_softc *sc)
{
struct pcpu *pc;
struct set_autonomous_hwp_cb data;
device_t dev;
dev = sc->dev;
pc = cpu_get_pcpu(dev);
if (pc == NULL)
return (ENXIO);
set_autonomous_hwp_send_one(sc, &data);
if (data.flag | HWP_ERROR_CPPC_ENABLE) {
device_printf(dev, "Failed to enable HWP for cpu%d (%d)\n",
pc->pc_cpuid, EFAULT);
goto out;
}
if (data.flag | HWP_ERROR_CPPC_REQUEST) {
device_printf(dev,
"Failed to read HWP request MSR for cpu%d (%d)\n",
pc->pc_cpuid, EFAULT);
goto out;
}
if (data.flag | HWP_ERROR_CPPC_CAPS) {
device_printf(dev,
"Failed to read HWP capabilities MSR for cpu%d (%d)\n",
pc->pc_cpuid, EFAULT);
goto out;
}
if (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" : "", pc->pc_cpuid);
goto out;
}
if (data.flag | HWP_ERROR_CPPC_REQUEST_PKG)
device_printf(dev,
"Failed to set autonomous HWP for package\n");
out:
thread_lock(curthread);
sched_unbind(curthread);
thread_unlock(curthread);
return (ret);
if (data.flag)
return (EFAULT);
return (0);
}
static int
intel_hwpstate_attach(device_t dev)
{
struct hwp_softc *sc;
int ret;
sc = device_get_softc(dev);
sc->dev = dev;
@@ -512,10 +607,7 @@ intel_hwpstate_attach(device_t dev)
if (cpu_power_ecx & CPUID_PERF_BIAS)
sc->hwp_perf_bias = true;
ret = set_autonomous_hwp(sc);
if (ret)
return (ret);
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,
@@ -534,7 +626,6 @@ intel_hwpstate_attach(device_t dev)
static int
intel_hwpstate_detach(device_t dev)
{
return (cpufreq_unregister(dev));
}
@@ -582,6 +673,59 @@ intel_hwpstate_suspend(device_t dev)
return (0);
}
struct hwpstate_resume_cb {
struct hwp_softc *sc;
uint32_t flag;
};
static void
hwpstate_resume_cb(void *arg)
{
struct hwpstate_resume_cb *data = arg;
struct hwp_softc *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(struct hwp_softc *sc, struct hwpstate_resume_cb *req)
{
req->sc = sc;
smp_rendezvous_cpu(cpu_get_pcpu(sc->dev)->pc_cpuid,
smp_no_rendezvous_barrier, hwpstate_resume_cb,
smp_no_rendezvous_barrier, req);
}
/*
* Redo a subset of set_autonomous_hwp on resume; untested. Without this,
* testers observed that on resume MSR_IA32_HWP_REQUEST was bogus.
@@ -591,7 +735,7 @@ intel_hwpstate_resume(device_t dev)
{
struct hwp_softc *sc;
struct pcpu *pc;
int ret;
struct hwpstate_resume_cb data;
sc = device_get_softc(dev);
@@ -599,52 +743,35 @@ intel_hwpstate_resume(device_t dev)
if (pc == NULL)
return (ENXIO);
thread_lock(curthread);
sched_bind(curthread, pc->pc_cpuid);
thread_unlock(curthread);
ret = wrmsr_safe(MSR_IA32_PM_ENABLE, 1);
if (ret) {
hwpstate_resume_send_one(sc, &data);
if (data.flag | HWP_ERROR_CPPC_ENABLE) {
device_printf(dev,
"Failed to enable HWP for cpu%d after suspend (%d)\n",
pc->pc_cpuid, ret);
pc->pc_cpuid, EFAULT);
goto out;
}
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) {
if (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" : "", pc->pc_cpuid);
goto out;
}
if (sc->hwp_pkg_ctrl_en) {
ret = wrmsr_safe(MSR_IA32_HWP_REQUEST_PKG, sc->req);
if (ret) {
device_printf(dev,
"Failed to set autonomous HWP for package after "
"suspend\n");
goto out;
}
if (data.flag | HWP_ERROR_CPPC_REQUEST_PKG) {
device_printf(dev,
"Failed to set autonomous HWP for package after "
"suspend\n");
goto out;
}
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) {
device_printf(dev,
"Failed to set energy perf bias for cpu%d after "
"suspend\n", pc->pc_cpuid);
}
if (data.flag | HWP_ERROR_CPPC_EPP_WRITE) {
device_printf(dev,
"Failed to set energy perf bias for cpu%d after "
"suspend\n",
pc->pc_cpuid);
}
out:
thread_lock(curthread);
sched_unbind(curthread);
thread_unlock(curthread);
return (ret);
if (data.flag)
return (EFAULT);
return (0);
}