blob: 1a96c5870456588aa7a10ca63f4a867939e256be [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors
//
// 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 "system_priv.h"
#include <arch/arch_ops.h>
#include <arch/mp.h>
#include <fbl/auto_call.h>
#include <inttypes.h>
#include <kernel/timer.h>
#include <lk/init.h>
#include <platform.h>
#include <trace.h>
#include <vm/vm_aspace.h>
#include <arch/x86/feature.h>
#include <arch/x86/bootstrap16.h>
#include <arch/x86/acpi.h>
extern "C" {
#include <acpica/acpi.h>
#include <acpica/accommon.h>
#include <acpica/achware.h>
}
#define LOCAL_TRACE 0
namespace {
#define ACPI_MAX_INIT_TABLES 32
//static ACPI_TABLE_DESC acpi_tables[ACPI_MAX_INIT_TABLES];
static bool acpi_initialized = false;
/**
* @brief Initialize early-access ACPI tables
*
* This function enables *only* the ACPICA Table Manager subsystem.
* The rest of the ACPI subsystem will remain uninitialized.
*/
void init_acpica_tables(uint level) {
DEBUG_ASSERT(!acpi_initialized);
ACPI_STATUS status;
status = AcpiInitializeTables(nullptr, ACPI_MAX_INIT_TABLES, FALSE);
if (status == AE_NOT_FOUND) {
TRACEF("WARNING: could not find ACPI tables\n");
return;
} else if (status == AE_NO_MEMORY) {
TRACEF("WARNING: could not initialize ACPI tables\n");
return;
} else if (status != AE_OK) {
TRACEF("WARNING: could not initialize ACPI tables for unknown reason\n");
return;
}
acpi_initialized = true;
LTRACEF("ACPI tables initialized\n");
}
LK_INIT_HOOK(acpica_tables, &init_acpica_tables, LK_INIT_LEVEL_THREADING);
// This thread performs the work for suspend/resume. We use a separate thread
// rather than the invoking thread to let us lean on the context switch code
// path to persist all of the usermode thread state that is not saved on a plain
// mode switch.
zx_status_t suspend_thread(void* raw_arg) {
auto arg = reinterpret_cast<const zx_system_powerctl_arg_t*>(raw_arg);
uint8_t target_s_state = arg->acpi_transition_s_state.target_s_state;
uint8_t sleep_type_a = arg->acpi_transition_s_state.sleep_type_a;
uint8_t sleep_type_b = arg->acpi_transition_s_state.sleep_type_b;
// Acquire resources for suspend and resume if necessary.
fbl::RefPtr<VmAspace> temp_aspace;
x86_realmode_entry_data* bootstrap_data;
struct x86_realmode_entry_data_registers regs;
paddr_t bootstrap_ip;
zx_status_t status;
status = x86_bootstrap16_acquire(reinterpret_cast<uintptr_t>(_x86_suspend_wakeup),
&temp_aspace,
reinterpret_cast<void**>(&bootstrap_data),
&bootstrap_ip);
if (status != ZX_OK) {
return status;
}
auto bootstrap_cleanup = fbl::MakeAutoCall([&bootstrap_data]() {
x86_bootstrap16_release(bootstrap_data);
});
// Setup our resume path
ACPI_TABLE_FACS* facs = nullptr;
ACPI_STATUS acpi_status = AcpiGetTable((char *)ACPI_SIG_FACS, 1,
reinterpret_cast<ACPI_TABLE_HEADER**>(&facs));
if (acpi_status != AE_OK) {
return ZX_ERR_BAD_STATE;
}
acpi_status = AcpiHwSetFirmwareWakingVector(facs, bootstrap_ip, 0);
if (acpi_status != AE_OK) {
return ZX_ERR_BAD_STATE;
}
auto wake_vector_cleanup = fbl::MakeAutoCall([facs]() {
AcpiHwSetFirmwareWakingVector(facs, 0, 0);
});
bootstrap_data->registers_ptr = reinterpret_cast<uintptr_t>(&regs);
arch_disable_ints();
// Save system state.
platform_suspend();
arch_suspend();
// Do the actual suspend
TRACEF("Entering x86_acpi_transition_s_state\n");
acpi_status = x86_acpi_transition_s_state(&regs, target_s_state,
sleep_type_a, sleep_type_b);
if (acpi_status != AE_OK) {
arch_enable_ints();
TRACEF("x86_acpi_transition_s_state failed: %x\n", acpi_status);
return ZX_ERR_INTERNAL;
}
TRACEF("Left x86_acpi_transition_s_state\n");
// If we're here, we've resumed and need to restore our CPU context
DEBUG_ASSERT(arch_ints_disabled());
arch_resume();
platform_resume();
timer_thaw_percpu();
DEBUG_ASSERT(arch_ints_disabled());
arch_enable_ints();
return ZX_OK;
}
zx_status_t x86_set_pkg_pl1(const zx_system_powerctl_arg_t* arg) {
if ((x86_microarch != X86_MICROARCH_INTEL_SANDY_BRIDGE) &&
(x86_microarch != X86_MICROARCH_INTEL_SILVERMONT) &&
(x86_microarch != X86_MICROARCH_INTEL_BROADWELL) &&
(x86_microarch != X86_MICROARCH_INTEL_HASWELL) &&
(x86_microarch != X86_MICROARCH_INTEL_SKYLAKE) &&
(x86_microarch != X86_MICROARCH_INTEL_KABYLAKE)) {
return ZX_ERR_NOT_SUPPORTED;
}
uint32_t power_limit = arg->x86_power_limit.power_limit;
uint8_t clamp = arg->x86_power_limit.clamp;
uint8_t enable = arg->x86_power_limit.enable;
uint64_t u = read_msr(X86_MSR_RAPL_POWER_UNIT);
uint64_t v = read_msr(X86_MSR_PKG_POWER_LIMIT);
uint64_t pu = 1 << (u & 0xf);
// TODO(ZX-1429) time window is not currently supported
v &= ~0x7fff;
if (power_limit > 0) {
uint64_t n = (power_limit * pu / 1000);
if (n > 0x7fff) {
return ZX_ERR_INVALID_ARGS;
}
v |= n;
} else {
// set to default if 0
v |= read_msr(X86_MSR_PKG_POWER_INFO) & 0x7fff;
}
if (clamp) {
v |= X86_MSR_PKG_POWER_LIMIT_PL1_CLAMP;
} else {
v &= ~X86_MSR_PKG_POWER_LIMIT_PL1_CLAMP;
}
if (enable) {
v |= X86_MSR_PKG_POWER_LIMIT_PL1_ENABLE;
} else {
v &= ~X86_MSR_PKG_POWER_LIMIT_PL1_ENABLE;
}
write_msr(X86_MSR_PKG_POWER_LIMIT, v);
return ZX_OK;
}
zx_status_t acpi_transition_s_state(const zx_system_powerctl_arg_t* arg) {
if (!acpi_initialized) {
return ZX_ERR_BAD_STATE;
}
uint8_t target_s_state = arg->acpi_transition_s_state.target_s_state;
uint8_t sleep_type_a = arg->acpi_transition_s_state.sleep_type_a;
uint8_t sleep_type_b = arg->acpi_transition_s_state.sleep_type_b;
if (target_s_state == 0 || target_s_state > 5) {
TRACEF("Bad S-state: S%u\n", target_s_state);
return ZX_ERR_INVALID_ARGS;
}
// If not a shutdown, ensure CPU 0 is the only cpu left running.
if (target_s_state != 5 && mp_get_online_mask() != cpu_num_to_mask(0)) {
TRACEF("Too many CPUs running for state S%u\n", target_s_state);
return ZX_ERR_BAD_STATE;
}
// Acquire resources for suspend and resume if necessary.
if (target_s_state < 5) {
// If we're not shutting down, prepare a resume path and execute the
// suspend on a separate thread (see comment on |suspend_thread()| for
// explanation).
thread_t* t = thread_create("suspend-thread", suspend_thread,
const_cast<zx_system_powerctl_arg_t*>(arg),
HIGHEST_PRIORITY);
if (!t) {
return ZX_ERR_NO_MEMORY;
}
thread_resume(t);
zx_status_t retcode;
zx_status_t status = thread_join(t, &retcode, ZX_TIME_INFINITE);
ASSERT(status == ZX_OK);
if (retcode != ZX_OK) {
return retcode;
}
} else {
struct x86_realmode_entry_data_registers regs;
DEBUG_ASSERT(target_s_state == 5);
arch_disable_ints();
auto acpi_status = x86_acpi_transition_s_state(&regs, target_s_state,
sleep_type_a, sleep_type_b);
arch_enable_ints();
if (acpi_status != 0) {
return ZX_ERR_INTERNAL;
}
}
return ZX_OK;
}
} // namespace
zx_status_t arch_system_powerctl(uint32_t cmd, const zx_system_powerctl_arg_t* arg) {
switch (cmd) {
case ZX_SYSTEM_POWERCTL_ACPI_TRANSITION_S_STATE:
return acpi_transition_s_state(arg);
case ZX_SYSTEM_POWERCTL_X86_SET_PKG_PL1:
return x86_set_pkg_pl1(arg);
default:
return ZX_ERR_NOT_SUPPORTED;
}
}