#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: psci.c,v 1.9 2026/05/15 22:44:58 jmcneill Exp $");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/xcall.h>
#include <arm/arm/psci.h>
#define PSCI_VERSION 0x84000000
#define PSCI_CPU_OFF 0x84000002
#define PSCI_SYSTEM_OFF 0x84000008
#define PSCI_SYSTEM_RESET 0x84000009
#define PSCI_FEATURES 0x8400000a
#if defined(__aarch64__)
#define PSCI_CPU_SUSPEND 0xc4000001
#define PSCI_CPU_ON 0xc4000003
#define PSCI_AFFINITY_INFO 0xc4000004
#else
#define PSCI_CPU_SUSPEND 0x84000001
#define PSCI_CPU_ON 0x84000003
#define PSCI_AFFINITY_INFO 0x84000004
#endif
static psci_fn psci_call_fn;
static uint32_t psci_functions[PSCI_FUNC_MAX] = {
[PSCI_FUNC_VERSION] = PSCI_VERSION,
[PSCI_FUNC_SYSTEM_OFF] = PSCI_SYSTEM_OFF,
[PSCI_FUNC_SYSTEM_RESET] = PSCI_SYSTEM_RESET,
[PSCI_FUNC_CPU_SUSPEND] = PSCI_CPU_SUSPEND,
[PSCI_FUNC_CPU_ON] = PSCI_CPU_ON,
[PSCI_FUNC_CPU_OFF] = PSCI_CPU_OFF,
[PSCI_FUNC_AFFINITY_INFO] = PSCI_AFFINITY_INFO,
[PSCI_FUNC_FEATURES] = PSCI_FEATURES,
};
int
psci_call(register_t fid, register_t arg1, register_t arg2, register_t arg3)
{
KASSERT(psci_call_fn != NULL);
if (fid == 0)
return PSCI_NOT_SUPPORTED;
return psci_call_fn(fid, arg1, arg2, arg3);
}
enum psci_conduit
psci_conduit(void)
{
if (psci_call_fn == psci_call_smc) {
return PSCI_CONDUIT_SMC;
} else if (psci_call_fn == psci_call_hvc) {
return PSCI_CONDUIT_HVC;
} else {
return PSCI_CONDUIT_NONE;
}
}
uint32_t
psci_version(void)
{
if (psci_functions[PSCI_FUNC_VERSION] == 0) {
return __SHIFTIN(0, PSCI_VERSION_MAJOR) |
__SHIFTIN(1, PSCI_VERSION_MINOR);
}
return psci_call(psci_functions[PSCI_FUNC_VERSION], 0, 0, 0);
}
int
psci_cpu_on(register_t target_cpu, register_t entry_point_address,
register_t context_id)
{
return psci_call(psci_functions[PSCI_FUNC_CPU_ON], target_cpu,
entry_point_address, context_id);
}
int
psci_cpu_off(void)
{
return psci_call(psci_functions[PSCI_FUNC_CPU_OFF], 0, 0, 0);
}
int
psci_cpu_suspend(uint32_t power_state)
{
return psci_call(psci_functions[PSCI_FUNC_CPU_SUSPEND], power_state,
0, 0);
}
int
psci_affinity_info(uint64_t target_affinity, uint32_t lowest_affinity_level)
{
return psci_call(psci_functions[PSCI_FUNC_AFFINITY_INFO],
target_affinity, lowest_affinity_level, 0);
}
void
psci_system_off(void)
{
psci_call(psci_functions[PSCI_FUNC_SYSTEM_OFF], 0, 0, 0);
}
void
psci_system_reset(void)
{
psci_call(psci_functions[PSCI_FUNC_SYSTEM_RESET], 0, 0, 0);
}
int
psci_features(uint32_t fid)
{
if (psci_functions[PSCI_FUNC_FEATURES] == 0) {
return PSCI_NOT_SUPPORTED;
}
return psci_call(psci_functions[PSCI_FUNC_FEATURES], fid, 0, 0);
}
void
psci_init(psci_fn fn)
{
psci_call_fn = fn;
}
bool
psci_available(void)
{
return psci_call_fn != NULL;
}
void
psci_clearfunc(void)
{
for (int i = 0; i < PSCI_FUNC_MAX; i++)
psci_setfunc(i, 0);
}
void
psci_setfunc(enum psci_function func, uint32_t fid)
{
psci_functions[func] = fid;
}
static void
psci_cpu_off_xcall(void *arg1, void *arg2)
{
if (curcpu() != arg1) {
aprint_debug("%s: power down\n", cpu_name(curcpu()));
psci_cpu_off();
printf("WARNING: %s power down failed\n", cpu_name(curcpu()));
}
}
void
psci_poweroff(void)
{
if (ncpuonline > 1) {
CPU_INFO_ITERATOR cii;
struct cpu_info *ci;
int ret;
xc_broadcast(0, psci_cpu_off_xcall, curcpu(), NULL);
for (CPU_INFO_FOREACH(cii, ci)) {
if (ci == curcpu()) {
continue;
}
for (;;) {
ret = psci_affinity_info(ci->ci_cpuid, 0);
if (ret < 0) {
printf("WARNING: %s state %d\n",
cpu_name(ci), ret);
}
if (ret != PSCI_AFFINITY_INFO_ON) {
break;
}
}
}
}
psci_system_off();
}