blob: c70d2bd7fdfe1e4af826b0595dff51147d440bbe [file]
// 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 <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 <kernel/lockdep.h>
#include <kernel/mp.h>
#include <kernel/spinlock.h>
DECLARE_SINGLETON_MUTEX(hwp_lock);
static uint8_t x86_intel_epb_to_epp(uint8_t epb) {
// 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.
static constexpr uint8_t energy_perf_bias_to_energy_perf_preference[] = {
0x20, // ENERGY_PERF_BIAS 0x0 is 'PERFORMANCE'
0x20,
0x20,
0x20,
0x40, // ENERGY_PERF_BIAS 0x4 is 'BALANCED PERFORMANCE'
0x40,
0x80, // ENERGY_PERF_BIAS 0x6 is 'NORMAL'
0x80,
0x80, // ENERGY_PERF_BIAS 0x8 is 'BALANCED POWERSAVE'
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF,
0xFF // ENERGY_PERF_BIAS 0xF is 'POWERSAVE'
};
static_assert(sizeof(energy_perf_bias_to_energy_perf_preference) == 16, "");
epb &= 0xF; // Sanitize ENERGY_PERF_BIAS just in case.
return energy_perf_bias_to_energy_perf_preference[epb];
}
void x86_intel_hwp_init(const cpu_id::CpuId* cpuid, MsrAccess* msr) {
static constexpr uint8_t kBalancedEPP = 0x80; // default
if (!cpuid->ReadFeatures().HasFeature(cpu_id::Features::HWP_PREF)) {
return;
}
// If available, use firmware-set IA32_ENERGY_PERF_BIAS to select Energy/Performance Preference
bool has_epb = cpuid->ReadFeatures().HasFeature(cpu_id::Features::EPB);
uint8_t epb;
if (has_epb) {
epb = msr->read_msr(X86_MSR_IA32_ENERGY_PERF_BIAS) & 0xF;
}
// Enable HWP
msr->write_msr(X86_MSR_IA32_PM_ENABLE, 1);
// Set minimum/maximum to values from capabilities for common case, set Desired_Performance to 0,
// and set energy performance preference from IA32_ENERGY_PERF_BIAS (if set) or to the default
// value if not.
// Reference: Intel SDM vol 3B section 14.4.10: Recommendations for OS use of HWP controls
uint64_t hwp_caps = msr->read_msr(X86_MSR_IA32_HWP_CAPABILITIES);
uint8_t max_performance = hwp_caps & 0xff;
uint8_t min_performance = (hwp_caps >> 24) & 0xff;
uint8_t energy_perf_preference = has_epb ? x86_intel_epb_to_epp(epb) : kBalancedEPP;
uint64_t hwp_req = static_cast<uint32_t>(energy_perf_preference << 24u) |
(max_performance << 8) | (min_performance);
msr->write_msr(X86_MSR_IA32_HWP_REQUEST, hwp_req);
}
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;
}
STATIC_COMMAND_START
STATIC_COMMAND("hwp", "hardware controlled performance states\n", &cmd_hwp)
STATIC_COMMAND_END(hwp)