blob: 3cb02920fc466fb103bce2b453232a9dfa85c81a [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 <inttypes.h>
#include <lib/console.h>
#include <lib/counters.h>
#include <lib/zbi-format/driver-config.h>
#include <string.h>
#include <trace.h>
#include <arch/arm64.h>
#include <arch/arm64/smccc.h>
#include <dev/psci.h>
#include <ktl/atomic.h>
#include <pdev/power.h>
#include <vm/handoff-end.h>
#define LOCAL_TRACE 0
namespace {
bool psci_set_suspend_mode_supported = false;
// The number of PSCI CPU_SUSPEND calls that completed successfully and resulted in a powered down
// state.
KCOUNTER(counter_psci_cpu_suspend_powered_down, "psci.cpu_suspend.ok.powered_down")
// The number of PSCI CPU_SUSPEND calls that completed successfully, but where state was retained
// (i.e. did not reach a powered down state for whatever reason).
KCOUNTER(counter_psci_cpu_suspend_retained, "psci.cpu_suspend.ok.retained")
// The number of PSCI CPU_SUSPEND calls that failed with PSCI_INVALID_PARAMETERS.
KCOUNTER(counter_psci_cpu_suspend_invalid_parameters, "psci.cpu_suspend.invalid_parameters")
// The number of PSCI CPU_SUSPEND calls that failed with PSCI_DENIED.
KCOUNTER(counter_psci_cpu_suspend_denied, "psci.cpu_suspend.psci_denied")
uint64_t shutdown_args[3] = {0, 0, 0};
uint64_t reboot_args[3] = {0, 0, 0};
uint64_t reboot_bootloader_args[3] = {0, 0, 0};
uint64_t reboot_recovery_args[3] = {0, 0, 0};
uint32_t reset_command = PSCI64_SYSTEM_RESET;
// Specifies which power_state format is used by this PSCI implementation.
enum class PowerStateFormat {
Unknown,
Original, // Section 5.4.2.1 of "Arm Power State Coordination Interface", DEN0022F.b.
Extended, // Section 5.4.2.2 of "Arm Power State Coordination Interface", DEN0022F.b.
};
PowerStateFormat power_state_format;
// Returns true iff |power_state| is a "powerdown" power_state (as opposed to a
// standby or retention state).
bool psci_is_powerdown_power_state(uint32_t power_state) {
switch (power_state_format) {
case PowerStateFormat::Original:
// In the original format, StateType is bit-16. If StateType is 1, it's a powerdown state.
return (power_state & (1 << 16)) != 0;
case PowerStateFormat::Extended:
// In the extended format, StateType is bit-30.
return (power_state & (1 << 30)) != 0;
default:
panic("unknown power_state_format %u", static_cast<uint32_t>(power_state_format));
};
}
// SuspendDomain has two jobs. First, it keeps track of the CPUs that are part of a given "power
// domain" (in the PSCI CPU_SUSPEND sense). Second, it provides the correct power state value for a
// given CPU_SUSPEND operation based on the current state of the domain.
//
// SuspendDomain is designed to handle race conditions associated with current CPU_SUSPEND
// operations that might otherwise result in the domain being "left on" when it should not be.
// Note, even with SuspendDomain, some race conditions exist, however, the PSCI implementation is
// designed to handle these by denying invalid CPU_SUSPEND requests with PSCI_DENIED. See section
// 6.2 of "Arm Power State Coordination Interface", DEN0022F.b.
//
// When a CPU is powered on, it must be registered with its SuspendDomain instance. Likewise, just
// before it's powered off, it must be unregistered.
//
// An instance of SuspendDomain is safe for concurrent use (i.e. it's thread-safe).
class SuspendDomain {
public:
SuspendDomain() = default;
// Initializes this instance with the given, optional, power state values. An instance must be
// initialized exactly once.
//
// If no value is provided for |power_state_target_cpu|, suspend support will be disabled. See
// |IsCpuSuspendSupported|.
//
// It is an error to provide a value for |power_state_target_power_domain|, without also providing
// a value for |power_state_target_cpu|.
void Init(ktl::optional<uint32_t> power_state_target_cpu,
ktl::optional<uint32_t> power_state_target_power_domain) {
DEBUG_ASSERT(power_state_target_cpu.has_value() ||
!power_state_target_power_domain.has_value());
DEBUG_ASSERT(target_cpu_ == ktl::nullopt);
target_cpu_ = power_state_target_cpu;
target_cpu_is_powerdown_state_ =
target_cpu_.has_value() ? psci_is_powerdown_power_state(power_state_target_cpu.value())
: false;
DEBUG_ASSERT(target_power_domain_ == ktl::nullopt);
target_power_domain_ = power_state_target_power_domain;
target_power_domain_is_powerdown_state_ =
target_power_domain_.has_value()
? psci_is_powerdown_power_state(power_state_target_power_domain.value())
: false;
}
// Registers a CPU with this instance.
//
// This method should be called once for each CPU shortly after it is powered on.
void RegisterCpu() {
[[maybe_unused]] uint32_t count = count_.fetch_add(1, ktl::memory_order_relaxed);
DEBUG_ASSERT(count < SMP_MAX_CPUS);
}
// Unregisters a CPU with this instance.
//
// This method should be called once for each CPU just before it is powered off.
void UnregisterCpu() {
[[maybe_unused]] uint32_t count = count_.fetch_add(-1, ktl::memory_order_relaxed);
DEBUG_ASSERT(count > 0);
}
// Returns true if PSCI CPU_SUSPEND is supported for this domain.
//
// Requirements for PSCI CPU_SUSPEND support:
//
// * The device has PSCI version 1.0 or better, and
// * PSCI FEATURES reports that CPU_ CPU_SUSPEND is supported, and
// * ZBI contains a supported CPU suspend state configuration.
//
// The ZBI-supplied CPU suspend state configuration is considered supported if it contains at
// least one entry where:
//
// * No unknown flags are specified, and
// * ZBI_ARM_PSCI_CPU_SUSPEND_STATE_FLAGS_TARGETS_POWER_DOMAIN is not set.
bool IsCpuSuspendSupported() const { return target_cpu_.has_value(); }
// Returns true if a call to CPU_SUSPEND might result in a powerdown state.
bool SuspendMightPowerdown() const {
return target_cpu_is_powerdown_state_ || target_power_domain_is_powerdown_state_;
}
// An RAII helper intended to be instantiated in a scope just before issuing a PSCI CPU_SUSPEND.
// Used to obtain the "best" power state value given the number of currently
// running-but-not-yet-suspended CPUs for the domain.
//
// E.g.
//
// {
// SuspendDomain::Guard(domain, max_scope);
// psci_do_suspend(..., guard.power_state(), ...);
// }
//
class Guard {
public:
// Construct a Guard for the given |domain|.
//
// |max_scope| controls whether the value returned by |power_state()| applies to just the
// calling CPU or to the enclosing power domain.
Guard(SuspendDomain& domain, PsciCpuSuspendMaxScope max_scope);
~Guard();
// Returns the "best" power state value for the PSCI CPU_SUSPEND in the calling scope.
uint32_t power_state() const { return power_state_; }
Guard(Guard&&) = delete;
Guard& operator=(Guard&&) = delete;
Guard(const Guard&) = delete;
Guard& operator=(const Guard&) = delete;
private:
SuspendDomain& domain_;
uint32_t power_state_;
};
SuspendDomain(SuspendDomain&&) = delete;
SuspendDomain& operator=(SuspendDomain&&) = delete;
SuspendDomain(const SuspendDomain&) = delete;
SuspendDomain& operator=(const SuspendDomain&) = delete;
private:
friend class Guard;
// When supported, this variable contains a power_state value to be passed in a CPU_SUSPEND call.
// When not supported, this variable contains ktl::nullopt.
ktl::optional<uint32_t> target_cpu_;
// Optionally contains a PSCI CPU_SUSPEND power_state value that targets a power domain (think
// cluster or whole system). Will only contain a value if |target_cpu_| contains a value.
ktl::optional<uint32_t> target_power_domain_;
// The number of powered-on-but-not-suspended CPUs.
ktl::atomic<uint32_t> count_{};
// Whether |target_cpu_| is present and is a powerdown state.
bool target_cpu_is_powerdown_state_{};
// Whether |targetpower_domain_| is present and is a powerdown state.
bool target_power_domain_is_powerdown_state_{};
};
SuspendDomain::Guard::Guard(SuspendDomain& domain, PsciCpuSuspendMaxScope max_scope)
: domain_(domain) {
DEBUG_ASSERT(domain.IsCpuSuspendSupported());
uint32_t count = domain_.count_.fetch_add(-1, ktl::memory_order_relaxed);
DEBUG_ASSERT(count >= 1);
if (count == 1 && max_scope == PsciCpuSuspendMaxScope::CpuAndMore &&
domain_.target_power_domain_.has_value()) {
power_state_ = domain_.target_power_domain_.value();
} else {
DEBUG_ASSERT(domain_.target_cpu_.has_value());
power_state_ = domain_.target_cpu_.value();
}
}
SuspendDomain::Guard::~Guard() { domain_.count_.fetch_add(1, ktl::memory_order_relaxed); }
SuspendDomain gSuspendDomain;
zx_status_t psci_status_to_zx_status(uint64_t psci_result);
zx_status_t psci_status_to_zx_status(uint64_t psci_result) {
int64_t status = static_cast<int64_t>(psci_result);
switch (status) {
case PSCI_SUCCESS:
return ZX_OK;
case PSCI_NOT_SUPPORTED:
case PSCI_DISABLED:
return ZX_ERR_NOT_SUPPORTED;
case PSCI_INVALID_PARAMETERS:
return ZX_ERR_INVALID_ARGS;
case PSCI_INVALID_ADDRESS:
return ZX_ERR_OUT_OF_RANGE;
case PSCI_DENIED:
return ZX_ERR_ACCESS_DENIED;
case PSCI_ALREADY_ON:
return ZX_ERR_BAD_STATE;
case PSCI_ON_PENDING:
return ZX_ERR_SHOULD_WAIT;
case PSCI_INTERNAL_FAILURE:
return ZX_ERR_INTERNAL;
case PSCI_NOT_PRESENT:
return ZX_ERR_NOT_FOUND;
default:
return ZX_ERR_BAD_STATE;
}
}
uint64_t psci_smc_call(uint32_t function, uint64_t arg0, uint64_t arg1, uint64_t arg2) {
LTRACEF("0x%x 0x%" PRIx64 " 0x%" PRIx64 " 0x%" PRIx64 "\n", function, arg0, arg1, arg2);
return arm_smccc_smc(function, arg0, arg1, arg2, 0, 0, 0, 0).x0;
}
uint64_t psci_hvc_call(uint32_t function, uint64_t arg0, uint64_t arg1, uint64_t arg2) {
return arm_smccc_hvc(function, arg0, arg1, arg2, 0, 0, 0, 0).x0;
}
using psci_call_proc = uint64_t (*)(uint32_t, uint64_t, uint64_t, uint64_t);
psci_call_proc do_psci_call = psci_smc_call;
// Parse and print |config|, setting the relevant global power state variables.
//
// Intended to be called once as part of |PsciInit| after |power_state_format| has bee set.
void parse_cpu_suspend_power_states(ktl::span<const zbi_dcfg_arm_psci_cpu_suspend_state_t> config) {
DEBUG_ASSERT(power_state_format != PowerStateFormat::Unknown);
if (config.empty()) {
dprintf(INFO, "PSCI power_states: none\n");
return;
}
constexpr uint32_t kKnownFlags = ZBI_ARM_PSCI_CPU_SUSPEND_STATE_FLAGS_LOCAL_TIMER_STOPS |
ZBI_ARM_PSCI_CPU_SUSPEND_STATE_FLAGS_TARGETS_POWER_DOMAIN;
dprintf(INFO, "PSCI power_state format: %s\n",
power_state_format == PowerStateFormat::Original ? "original" : "extended");
dprintf(INFO, "PSCI power_states:\n");
ktl::optional<uint32_t> target_cpu;
ktl::optional<uint32_t> target_power_domain;
// Iterate over every element. The elements are in no particular order. The last-one-wins
// behavior of this loop is somewhat arbitrary.
for (const zbi_dcfg_arm_psci_cpu_suspend_state_t& elem : config) {
// Ignore any power state that contains flags we don't know about as they may indicate a
// requirement that must be met before invoking that power state.
const bool skip = elem.flags & ~kKnownFlags;
dprintf(INFO, " id=0x%x, power_state=0x%x, flags=0x%x, powerdown=%d%s\n", elem.id,
elem.power_state, elem.flags, psci_is_powerdown_power_state(elem.power_state),
skip ? " SKIPPED" : "");
dprintf(INFO, " entry_latency_us=%u, exit_latency_us=%u, min_residency_us=%u\n",
elem.entry_latency_us, elem.exit_latency_us, elem.min_residency_us);
if (skip) {
continue;
}
if ((elem.flags & ZBI_ARM_PSCI_CPU_SUSPEND_STATE_FLAGS_TARGETS_POWER_DOMAIN) == 0) {
target_cpu = elem.power_state;
} else {
target_power_domain = elem.power_state;
}
}
// Initialize the one and only suspend domain and register the calling CPU.
gSuspendDomain.Init(target_cpu, target_power_domain);
gSuspendDomain.RegisterCpu();
}
} // anonymous namespace
// Saves register state in |context|, then issues a PSCI call, using
// |psci_call|, for the specified |psci_func|, passing along the supplied
// |power_state|, |entry|, and |context| arguments.
//
// This function is designed to be called with PSCI64_CPU_SUSPEND and work with
// |psci_do_resume| and |arm64_secondary_start|.
//
// In all cases, this function will always appear to return to its caller.
// Depending on the PSCI implementation and the conditions, it may do so
// directly, or in the case of a successful CPU_SUSPEND, it may do so via
// |arm64_secondary_start| and |psci_do_resume|.
//
// If the return value is less than or equal to zero, then control did not
// branch to |entry| and the return value should be interpreted as a PSCI return
// value (see section 5.4.5 of DEN0022F.b):
//
// PSCI_SUCCESS - The CPU_SUSPEND call succeeded. However, the CPU did not
// reach a powerdown state because of a pending interrupt, or simply because
// the requested |power_state| is not a powerdown state.
//
// PSCI_INVALID_PARAMETERS - The |power_state| is invalid, or a low-power
// state was requested for a higher-than-core-level topology node
// (e.g. cluster) and at least one of the children in that node is in a local
// low-power state that is incompatible with the request.
//
// PSCI_DENIED - A low-power state is requested for a higher-than-core-level
// topology node (e.g. cluster) and all the cores that are in an incompatible
// state with the request are running, as opposed to being in a low-power
// state.
//
// PSCI_INVALID_ADDRESS - |entry| is not a valid physical address.
//
// If the return value is greater than zero, then the CPU did in fact suspend
// and resume execution at |entry| before returning to the caller.
//
// Implemented in assembly.
extern "C" int64_t psci_do_suspend(psci_call_proc psci_call, uint32_t power_state, paddr_t entry,
psci_cpu_resume_context* context);
extern "C" zx_status_t psci_do_resume(psci_cpu_resume_context* context) __NO_RETURN;
zx_status_t psci_system_off() {
return psci_status_to_zx_status(
do_psci_call(PSCI64_SYSTEM_OFF, shutdown_args[0], shutdown_args[1], shutdown_args[2]));
}
uint32_t psci_get_version() {
return static_cast<uint32_t>(do_psci_call(PSCI64_PSCI_VERSION, 0, 0, 0));
}
/* powers down the calling cpu - only returns if call fails */
zx_status_t psci_cpu_off() {
gSuspendDomain.UnregisterCpu();
return psci_status_to_zx_status(do_psci_call(PSCI64_CPU_OFF, 0, 0, 0));
}
zx_status_t psci_cpu_on(uint64_t mpid, paddr_t entry, uint64_t context) {
LTRACEF("CPU_ON mpid %#" PRIx64 ", entry %#" PRIx64 "\n", mpid, entry);
gSuspendDomain.RegisterCpu();
return psci_status_to_zx_status(do_psci_call(PSCI64_CPU_ON, mpid, entry, context));
}
bool psci_might_powerdown() { return gSuspendDomain.SuspendMightPowerdown(); }
PsciCpuSuspendResult psci_cpu_suspend(PsciCpuSuspendMaxScope max_scope) {
LTRACE_ENTRY;
DEBUG_ASSERT(arch_ints_disabled());
if (!psci_is_cpu_suspend_supported()) {
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
const paddr_t entry_pa = KernelPhysicalAddressOf<arm64_secondary_start>();
psci_cpu_resume_context context{};
int64_t result;
uint32_t power_state;
{
SuspendDomain::Guard guard(gSuspendDomain, max_scope);
power_state = guard.power_state();
LTRACEF("cpu %u, psci_call_routine 0x%" PRIx64 ", power_state 0x%x, entry 0x%" PRIx64
", context %p\n",
arch_curr_cpu_num(), reinterpret_cast<uintptr_t>(do_psci_call), power_state, entry_pa,
&context);
result = psci_do_suspend(do_psci_call, power_state, entry_pa, &context);
}
if (result > 0) {
// We took the "long way" and restored CPU context from a powerdown state.
kcounter_add(counter_psci_cpu_suspend_powered_down, 1);
return zx::ok(CpuPoweredDown::Yes);
}
switch (result) {
case PSCI_SUCCESS:
kcounter_add(counter_psci_cpu_suspend_retained, 1);
return zx::ok(CpuPoweredDown::No);
case PSCI_INVALID_PARAMETERS:
kcounter_add(counter_psci_cpu_suspend_invalid_parameters, 1);
return zx::error(ZX_ERR_INVALID_ARGS);
case PSCI_DENIED:
kcounter_add(counter_psci_cpu_suspend_denied, 1);
return zx::error(ZX_ERR_ACCESS_DENIED);
default:
panic("cpu %u, psci_call_routine 0x%" PRIx64 ", power_state 0x%x, entry 0x%" PRIx64
", context %p\n",
arch_curr_cpu_num(), reinterpret_cast<uintptr_t>(do_psci_call), power_state, entry_pa,
&context);
};
}
int64_t psci_get_affinity_info(uint64_t mpid) {
return static_cast<int64_t>(do_psci_call(PSCI64_AFFINITY_INFO, mpid, 0, 0));
}
zx::result<power_cpu_state> psci_get_cpu_state(uint64_t mpid) {
int64_t aff_info = psci_get_affinity_info(mpid);
switch (aff_info) {
case 0:
return zx::success(power_cpu_state::ON);
case 1:
return zx::success(power_cpu_state::OFF);
case 2:
return zx::success(power_cpu_state::ON_PENDING);
default:
return zx::error(psci_status_to_zx_status(aff_info));
}
}
uint32_t psci_get_feature(uint32_t psci_call) {
return static_cast<uint32_t>(do_psci_call(PSCI64_PSCI_FEATURES, psci_call, 0, 0));
}
zx_status_t psci_system_reset2_raw(uint32_t reset_type, uint32_t cookie) {
dprintf(INFO, "PSCI SYSTEM_RESET2: %#" PRIx32 " %#" PRIx32 "\n", reset_type, cookie);
uint64_t psci_status = do_psci_call(PSCI64_SYSTEM_RESET2, reset_type, cookie, 0);
dprintf(INFO, "PSCI SYSTEM_RESET2 returns %" PRIi64 "\n", static_cast<int64_t>(psci_status));
return psci_status_to_zx_status(psci_status);
}
zx_status_t psci_system_reset(power_reboot_flags flags) {
uint64_t* args = reboot_args;
if (flags == power_reboot_flags::REBOOT_BOOTLOADER) {
args = reboot_bootloader_args;
} else if (flags == power_reboot_flags::REBOOT_RECOVERY) {
args = reboot_recovery_args;
}
dprintf(INFO, "PSCI reboot: %#" PRIx32 " %#" PRIx64 " %#" PRIx64 " %#" PRIx64 "\n", reset_command,
args[0], args[1], args[2]);
return psci_status_to_zx_status(do_psci_call(reset_command, args[0], args[1], args[2]));
}
zx_status_t psci_set_suspend_mode(psci_suspend_mode mode) {
return psci_status_to_zx_status(do_psci_call(PSCI64_PSCI_SET_SUSPEND_MODE, mode, 0, 0));
}
bool psci_is_set_suspend_mode_supported() { return psci_set_suspend_mode_supported; }
bool psci_is_cpu_suspend_supported() { return gSuspendDomain.IsCpuSuspendSupported(); }
void PsciInit(const zbi_dcfg_arm_psci_driver_t& config,
ktl::span<const zbi_dcfg_arm_psci_cpu_suspend_state_t> psci_cpu_suspend_config) {
do_psci_call = config.use_hvc ? psci_hvc_call : psci_smc_call;
memcpy(shutdown_args, config.shutdown_args, sizeof(shutdown_args));
memcpy(reboot_args, config.reboot_args, sizeof(reboot_args));
memcpy(reboot_bootloader_args, config.reboot_bootloader_args, sizeof(reboot_bootloader_args));
memcpy(reboot_recovery_args, config.reboot_recovery_args, sizeof(reboot_recovery_args));
// read information about the psci implementation
uint32_t result = psci_get_version();
uint32_t major = (result >> 16) & 0xffff;
uint32_t minor = result & 0xffff;
dprintf(INFO, "PSCI version %u.%u\n", major, minor);
if (major >= 1 && major != 0xffff) {
// query features
dprintf(INFO, "PSCI supported features:\n");
// Prints info about the features and returns supported flags or nullopt if not supported.
auto probe_feature = [](uint32_t feature, const char* feature_name) -> ktl::optional<uint32_t> {
uint32_t result = psci_get_feature(feature);
if (static_cast<int32_t>(result) < 0) {
// Not supported
return ktl::nullopt;
}
dprintf(INFO, "\t%s (0x%x)\n", feature_name, result);
return result;
};
const ktl::optional<uint32_t> cpu_suspend_result =
probe_feature(PSCI64_CPU_SUSPEND, "CPU_SUSPEND");
probe_feature(PSCI64_CPU_OFF, "CPU_OFF");
probe_feature(PSCI64_CPU_ON, "CPU_ON");
probe_feature(PSCI64_AFFINITY_INFO, "AFFINITY_INFO");
probe_feature(PSCI64_MIGRATE, "MIGRATE");
probe_feature(PSCI64_MIGRATE_INFO_TYPE, "MIGRATE_INFO_TYPE");
probe_feature(PSCI64_MIGRATE_INFO_UP_CPU, "MIGRATE_INFO_UP_CPU");
probe_feature(PSCI64_SYSTEM_OFF, "SYSTEM_OFF");
probe_feature(PSCI64_SYSTEM_RESET, "SYSTEM_RESET");
if (probe_feature(PSCI64_SYSTEM_RESET2, "SYSTEM_RESET2").has_value()) {
// Prefer RESET2 if present. It explicitly supports arguments, but some vendors have
// extended RESET to behave the same way.
reset_command = PSCI64_SYSTEM_RESET2;
}
probe_feature(PSCI64_CPU_FREEZE, "CPU_FREEZE");
probe_feature(PSCI64_CPU_DEFAULT_SUSPEND, "CPU_DEFAULT_SUSPEND");
probe_feature(PSCI64_NODE_HW_STATE, "NODE_HW_STATE");
probe_feature(PSCI64_SYSTEM_SUSPEND, "SYSTEM_SUSPEND");
psci_set_suspend_mode_supported =
probe_feature(PSCI64_PSCI_SET_SUSPEND_MODE, "PSCI_SET_SUSPEND_MODE").has_value();
probe_feature(PSCI64_PSCI_STAT_RESIDENCY, "PSCI_STAT_RESIDENCY");
probe_feature(PSCI64_PSCI_STAT_COUNT, "PSCI_STAT_COUNT");
probe_feature(PSCI64_MEM_PROTECT, "MEM_PROTECT");
probe_feature(PSCI64_MEM_PROTECT_RANGE, "MEM_PROTECT_RANGE");
probe_feature(PSCI64_SMCCC_VERSION, "PSCI64_SMCCC_VERSION");
// Print the power_state format and power_states after printing all the supported features.
if (cpu_suspend_result.has_value()) {
// Determine and set the power_state_format.
const uint32_t options = cpu_suspend_result.value();
// If bit-1 is zero, then it's original format.
if ((options & (1 << 1)) == 0) {
power_state_format = PowerStateFormat::Original;
} else {
power_state_format = PowerStateFormat::Extended;
}
parse_cpu_suspend_power_states(psci_cpu_suspend_config);
}
}
// Register with the pdev power driver.
static const pdev_power_ops psci_ops = {
.reboot = psci_system_reset,
.shutdown = psci_system_off,
.cpu_off = psci_cpu_off,
.cpu_on = psci_cpu_on,
.get_cpu_state = psci_get_cpu_state,
};
pdev_register_power(&psci_ops);
}
namespace {
int cmd_psci(int argc, const cmd_args* argv, uint32_t flags) {
if (argc < 2) {
notenoughargs:
printf("not enough arguments\n");
printf("%s system_reset\n", argv[0].str);
printf("%s system_off\n", argv[0].str);
printf("%s cpu_on <mpidr>\n", argv[0].str);
printf("%s affinity_info <cluster> <cpu>\n", argv[0].str);
printf("%s <function_id> [arg0] [arg1] [arg2]\n", argv[0].str);
return -1;
}
if (!strcmp(argv[1].str, "system_reset")) {
psci_system_reset(power_reboot_flags::REBOOT_NORMAL);
} else if (!strcmp(argv[1].str, "system_off")) {
psci_system_off();
} else if (!strcmp(argv[1].str, "cpu_on")) {
if (argc < 3) {
goto notenoughargs;
}
paddr_t secondary_entry_paddr = KernelPhysicalAddressOf<arm64_secondary_start>();
uint32_t ret = psci_cpu_on(argv[2].u, secondary_entry_paddr, 0);
printf("psci_cpu_on returns %u\n", ret);
} else if (!strcmp(argv[1].str, "affinity_info")) {
if (argc < 4) {
goto notenoughargs;
}
int64_t ret = psci_get_affinity_info(ARM64_MPID(argv[2].u, argv[3].u));
printf("affinity info returns %ld\n", ret);
} else {
uint32_t function = static_cast<uint32_t>(argv[1].u);
uint64_t arg0 = (argc >= 3) ? argv[2].u : 0;
uint64_t arg1 = (argc >= 4) ? argv[3].u : 0;
uint64_t arg2 = (argc >= 5) ? argv[4].u : 0;
uint64_t ret = do_psci_call(function, arg0, arg1, arg2);
printf("do_psci_call returned %" PRIu64 "\n", ret);
}
return 0;
}
STATIC_COMMAND_START
STATIC_COMMAND_MASKED("psci", "execute PSCI command", &cmd_psci, CMD_AVAIL_ALWAYS)
STATIC_COMMAND_END(psci)
} // anonymous namespace