blob: 8fac52e22b4dab9d1fd28ee7a4962e6317a54cbc [file] [log] [blame] [edit]
// Copyright 2017 The Fuchsia Authors
// Copyright (c) 2016 Travis Geiselbrecht
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include <bits.h>
#include <err.h>
#include <inttypes.h>
#include <lib/console.h>
#include <string.h>
#include <zircon/compiler.h>
#include <arch/x86/cpuid.h>
#include <arch/x86/feature.h>
#include <arch/x86/hwp.h>
#include <arch/x86/platform_access.h>
#include <fbl/hard_int.h>
#include <kernel/lockdep.h>
#include <kernel/mp.h>
#include <kernel/spinlock.h>
#include <ktl/optional.h>
#include <ktl/string_view.h>
DECLARE_SINGLETON_MUTEX(hwp_lock);
namespace x86 {
namespace {
// An "energy performance preference" is a 8-bit value specifying a desired
// tradeoff between running a CPU in a high performance mode (0) vs an
// energy-efficient mode (255).
DEFINE_HARD_INT(EnergyPerformancePref, uint8_t)
// Various EnergyPerformancePref values.
constexpr auto kMaxPerformanceEPP = EnergyPerformancePref(0x00);
constexpr auto kBalancedEPP = EnergyPerformancePref(0x80);
constexpr auto kPowerSaveEPP = EnergyPerformancePref(0xff);
// An 8-bit "performance level", as used by the IA32_HWP_CAPABILITIES
// MSR. Higher values indicate higher performance, at the cost of using
// more power.
DEFINE_HARD_INT(PerformanceLevel, uint8_t)
// Convert the 4-bit IA32_ENERGY_PERF_BIAS value into an 8-bit
// IA32_ENERGY_PERF_PREFERENCE value.
//
// IA32_ENERGY_PERF_BIAS is a 4-bit value that may be set by firmware to
// indicate a platform's desired tradeoff between performance and power
// efficiency. It is only used when HWP is not active, so we convert it to HWP's
// ENERGY_PERFORMANCE_PREFERENCE scale.
EnergyPerformancePref PerfBiasToPerfPref(uint8_t epb) {
static constexpr uint8_t energy_perf_bias_to_energy_perf_preference[] = {
/* 0x0 */ 0x20, // 'PERFORMANCE'
/* 0x1 */ 0x20,
/* 0x2 */ 0x20,
/* 0x3 */ 0x20,
/* 0x4 */ 0x40, // 'BALANCED PERFORMANCE'
/* 0x5 */ 0x40,
/* 0x6 */ 0x80, // 'NORMAL'
/* 0x7 */ 0x80,
/* 0x8 */ 0x80, // 'BALANCED POWERSAVE'
/* 0x9 */ 0xFF,
/* 0xA */ 0xFF,
/* 0xB */ 0xFF,
/* 0xC */ 0xFF,
/* 0xD */ 0xFF,
/* 0xE */ 0xFF,
/* 0xF */ 0xFF, // 'POWERSAVE'
};
static_assert(sizeof(energy_perf_bias_to_energy_perf_preference) == 16);
epb &= 0xF; // Sanitize ENERGY_PERF_BIAS just in case.
return EnergyPerformancePref(energy_perf_bias_to_energy_perf_preference[epb]);
}
// Hardware-recommended EnergencyPerformancePref values.
struct HwpCapabilities {
PerformanceLevel most_efficient_performance;
PerformanceLevel guaranteed_performance;
PerformanceLevel highest_performance;
PerformanceLevel lowest_performance;
};
// Parse the HWP capabilties of the CPU.
HwpCapabilities ReadHwpCapabilities(MsrAccess* msr) {
uint64_t hwp_caps = msr->read_msr(X86_MSR_IA32_HWP_CAPABILITIES);
HwpCapabilities result;
result.highest_performance = PerformanceLevel(ExtractBits<7, 0, uint8_t>(hwp_caps));
result.guaranteed_performance = PerformanceLevel(ExtractBits<15, 8, uint8_t>(hwp_caps));
result.most_efficient_performance = PerformanceLevel(ExtractBits<23, 16, uint8_t>(hwp_caps));
result.lowest_performance = PerformanceLevel(ExtractBits<31, 24, uint8_t>(hwp_caps));
return result;
}
// Return the EnergyPerformancePref recommended by the BIOS/firmware.
EnergyPerformancePref GetBiosEPP(const cpu_id::CpuId* cpuid, MsrAccess* msr) {
if (cpuid->ReadFeatures().HasFeature(cpu_id::Features::EPB)) {
return PerfBiasToPerfPref(msr->read_msr(X86_MSR_IA32_ENERGY_PERF_BIAS) & 0xF);
}
return kBalancedEPP;
}
// Construct a 64-bit MSR_REQUEST MSR value.
uint64_t MakeHwpRequest(PerformanceLevel min_perf, PerformanceLevel max_perf,
PerformanceLevel desired_perf, EnergyPerformancePref epp) {
return static_cast<uint64_t>(min_perf.value()) |
(static_cast<uint64_t>(max_perf.value()) << 8ull) |
(static_cast<uint64_t>(desired_perf.value()) << 16ull) |
(static_cast<uint64_t>(epp.value()) << 24ull);
}
} // namespace
ktl::optional<IntelHwpPolicy> IntelHwpParsePolicy(const char* str) {
if (str == nullptr) {
return ktl::nullopt;
}
static const constexpr struct IntelHwpPolicyName {
IntelHwpPolicy policy;
ktl::string_view name;
} kHwpPolicyNames[] = {
{IntelHwpPolicy::kBiosSpecified, "bios-specified"sv},
{IntelHwpPolicy::kPerformance, "performance"sv},
{IntelHwpPolicy::kBalanced, "balanced"sv},
{IntelHwpPolicy::kPowerSave, "power-save"sv},
{IntelHwpPolicy::kStablePerformance, "stable-performance"sv},
};
ktl::string_view s = ktl::string_view(str);
for (const IntelHwpPolicyName item : kHwpPolicyNames) {
if (s == item.name) {
return item.policy;
}
}
return ktl::nullopt;
}
void IntelHwpInit(const cpu_id::CpuId* cpuid, MsrAccess* msr, IntelHwpPolicy policy) {
// Ensure we have HWP on this CPU.
if (!IntelHwpSupported(cpuid)) {
return;
}
// Enable HWP.
msr->write_msr(X86_MSR_IA32_PM_ENABLE, 1);
// Get hardware capabilities.
HwpCapabilities caps = ReadHwpCapabilities(msr);
// Set up HWP preferences.
//
// In most cases, we set minimum/maximum to values from the corresponding
// capabilities, set desired performance to 0 ("automatic"), and set the
// energy performance based on the policy.
//
// Reference: Intel SDM vol 3B section 14.4.7: Recommendations for OS use of
// HWP controls
PerformanceLevel desired = PerformanceLevel(0); // auto
PerformanceLevel min = caps.lowest_performance;
PerformanceLevel max = caps.highest_performance;
EnergyPerformancePref pref = kMaxPerformanceEPP;
// Override defaults based on policy.
switch (policy) {
case IntelHwpPolicy::kBiosSpecified:
pref = GetBiosEPP(cpuid, msr);
break;
case IntelHwpPolicy::kPerformance:
pref = kMaxPerformanceEPP;
break;
case IntelHwpPolicy::kBalanced:
pref = kBalancedEPP;
break;
case IntelHwpPolicy::kPowerSave:
pref = kPowerSaveEPP;
break;
case IntelHwpPolicy::kStablePerformance:
// Set min/max/desired to "guaranteed_performance" to try and keep CPU at
// a stable performance level.
min = caps.guaranteed_performance;
max = caps.guaranteed_performance;
desired = caps.guaranteed_performance;
pref = kMaxPerformanceEPP;
break;
}
// Program the HWP request register.
msr->write_msr(X86_MSR_IA32_HWP_REQUEST, MakeHwpRequest(/*min_perf=*/min, /*max_perf=*/max,
/*desired_perf=*/desired, /*epp=*/pref));
}
bool IntelHwpSupported(const cpu_id::CpuId* cpuid) {
return cpuid->ReadFeatures().HasFeature(cpu_id::Features::HWP) &&
cpuid->ReadFeatures().HasFeature(cpu_id::Features::HWP_PREF);
}
static void hwp_set_hint_sync_task(void* ctx) {
uint8_t hint = (unsigned long)ctx & 0xff;
uint64_t hwp_req = read_msr(X86_MSR_IA32_HWP_REQUEST) & ~(0xff << 16);
hwp_req |= (hint << 16);
hwp_req &= ~(0xffffffffull << 32);
write_msr(X86_MSR_IA32_HWP_REQUEST, hwp_req);
}
static void hwp_set_desired_performance(unsigned long hint) {
Guard<Mutex> guard{hwp_lock::Get()};
if (!x86_feature_test(X86_FEATURE_HWP_PREF)) {
printf("HWP hint not supported\n");
return;
}
mp_sync_exec(MP_IPI_TARGET_ALL, 0, hwp_set_hint_sync_task, (void*)hint);
}
static int cmd_hwp(int argc, const cmd_args* argv, uint32_t flags) {
if (argc < 2) {
notenoughargs:
printf("not enough arguments\n");
usage:
printf("usage:\n");
printf("%s hint <1-255>: set clock speed hint (as a multiple of 100MHz)\n", argv[0].str);
printf("%s hint 0: enable autoscaling\n", argv[0].str);
return ZX_ERR_INTERNAL;
}
if (!strcmp(argv[1].str, "hint")) {
if (argc < 3) {
goto notenoughargs;
}
if (argv[2].u > 0xff) {
printf("hint must be between 0 and 255\n");
goto usage;
}
hwp_set_desired_performance(argv[2].u);
} else {
printf("unknown command\n");
goto usage;
}
return ZX_OK;
}
} // namespace x86
STATIC_COMMAND_START
STATIC_COMMAND("hwp", "hardware controlled performance states\n", &x86::cmd_hwp)
STATIC_COMMAND_END(hwp)