Implement minimal PSCI for secondary VMs to manage their vCPUs.
Bug: 132422393
Change-Id: I44643ec9eec722dfe0332b7ffefadcdd8dd98985
diff --git a/src/arch/aarch64/hypervisor/handler.c b/src/arch/aarch64/hypervisor/handler.c
index 7de6ad3..7943401 100644
--- a/src/arch/aarch64/hypervisor/handler.c
+++ b/src/arch/aarch64/hypervisor/handler.c
@@ -30,6 +30,7 @@
#include "msr.h"
#include "psci.h"
+#include "psci_handler.h"
#include "smc.h"
#define HCR_EL2_VI (1u << 7)
@@ -39,32 +40,6 @@
struct vcpu *new;
};
-void cpu_entry(struct cpu *c);
-
-static uint32_t el3_psci_version = 0;
-
-/* Performs arch specific boot time initialisation. */
-void arch_one_time_init(void)
-{
- el3_psci_version = smc(PSCI_VERSION, 0, 0, 0);
-
- /* Check there's nothing unexpected about PSCI. */
- switch (el3_psci_version) {
- case PSCI_VERSION_0_2:
- case PSCI_VERSION_1_0:
- case PSCI_VERSION_1_1:
- /* Supported EL3 PSCI version. */
- dlog("Found PSCI version: 0x%x\n", el3_psci_version);
- break;
-
- default:
- /* Unsupported EL3 PSCI version. Log a warning but continue. */
- dlog("Warning: unknown PSCI version: 0x%x\n", el3_psci_version);
- el3_psci_version = 0;
- break;
- }
-}
-
/* Gets a reference to the currently executing vCPU. */
static struct vcpu *current(void)
{
@@ -229,176 +204,6 @@
}
/**
- * Handles PSCI requests received via HVC or SMC instructions from the primary
- * VM only.
- *
- * A minimal PSCI 1.1 interface is offered which can make use of previous
- * version of PSCI in EL3 by acting as an adapter.
- *
- * Returns true if the request was a PSCI one, false otherwise.
- */
-static bool psci_handler(uint32_t func, uintreg_t arg0, uintreg_t arg1,
- uintreg_t arg2, int32_t *ret)
-{
- struct cpu *c;
-
- /*
- * If there's a problem with the EL3 PSCI, block standard secure service
- * calls by marking them as unknown. Other calls will be allowed to pass
- * through.
- *
- * This blocks more calls than just PSCI so it may need to be made more
- * lenient in future.
- */
- if (el3_psci_version == 0) {
- *ret = SMCCC_RETURN_UNKNOWN;
- return (func & SMCCC_SERVICE_CALL_MASK) ==
- SMCCC_STANDARD_SECURE_SERVICE_CALL;
- }
-
- switch (func & ~SMCCC_CONVENTION_MASK) {
- case PSCI_VERSION:
- *ret = PSCI_VERSION_1_1;
- break;
-
- case PSCI_FEATURES:
- switch (arg0 & ~SMCCC_CONVENTION_MASK) {
- case PSCI_CPU_SUSPEND:
- if (el3_psci_version == PSCI_VERSION_0_2) {
- /*
- * PSCI 0.2 doesn't support PSCI_FEATURES so
- * report PSCI 0.2 compatible features.
- */
- *ret = 0;
- } else {
- /* PSCI 1.x only defines two feature bits. */
- *ret = smc(func, arg0, 0, 0) & 0x3;
- }
- break;
-
- case PSCI_VERSION:
- case PSCI_FEATURES:
- case PSCI_SYSTEM_OFF:
- case PSCI_SYSTEM_RESET:
- case PSCI_AFFINITY_INFO:
- case PSCI_CPU_OFF:
- case PSCI_CPU_ON:
- /* These are supported without special features. */
- *ret = 0;
- break;
-
- default:
- /* Everything else is unsupported. */
- *ret = PSCI_RETURN_NOT_SUPPORTED;
- break;
- }
- break;
-
- case PSCI_SYSTEM_OFF:
- smc(PSCI_SYSTEM_OFF, 0, 0, 0);
- panic("System off failed");
- break;
-
- case PSCI_SYSTEM_RESET:
- smc(PSCI_SYSTEM_RESET, 0, 0, 0);
- panic("System reset failed");
- break;
-
- case PSCI_AFFINITY_INFO:
- c = cpu_find(arg0);
- if (!c) {
- *ret = PSCI_RETURN_INVALID_PARAMETERS;
- break;
- }
-
- if (arg1 != 0) {
- *ret = PSCI_RETURN_NOT_SUPPORTED;
- break;
- }
-
- sl_lock(&c->lock);
- if (c->is_on) {
- *ret = 0; /* ON */
- } else {
- *ret = 1; /* OFF */
- }
- sl_unlock(&c->lock);
- break;
-
- case PSCI_CPU_SUSPEND: {
- /*
- * Update vcpu state to wake from the provided entry point but
- * if suspend returns, for example because it failed or was a
- * standby power state, the SMC will return and the updated
- * vcpu registers will be ignored.
- */
- struct vcpu *vcpu = current();
-
- arch_regs_set_pc_arg(&vcpu->regs, ipa_init(arg1), arg2);
- *ret = smc(PSCI_CPU_SUSPEND | SMCCC_64_BIT, arg0,
- (uintreg_t)&cpu_entry, (uintreg_t)vcpu->cpu);
- break;
- }
-
- case PSCI_CPU_OFF:
- cpu_off(current()->cpu);
- smc(PSCI_CPU_OFF, 0, 0, 0);
- panic("CPU off failed");
- break;
-
- case PSCI_CPU_ON:
- c = cpu_find(arg0);
- if (!c) {
- *ret = PSCI_RETURN_INVALID_PARAMETERS;
- break;
- }
-
- if (cpu_on(c, ipa_init(arg1), arg2)) {
- *ret = PSCI_RETURN_ALREADY_ON;
- break;
- }
-
- /*
- * There's a race when turning a CPU on when it's in the
- * process of turning off. We need to loop here while it is
- * reported that the CPU is on (because it's about to turn
- * itself off).
- */
- do {
- *ret = smc(PSCI_CPU_ON | SMCCC_64_BIT, arg0,
- (uintreg_t)&cpu_entry, (uintreg_t)c);
- } while (*ret == PSCI_RETURN_ALREADY_ON);
-
- if (*ret != PSCI_RETURN_SUCCESS) {
- cpu_off(c);
- }
- break;
-
- case PSCI_MIGRATE:
- case PSCI_MIGRATE_INFO_TYPE:
- case PSCI_MIGRATE_INFO_UP_CPU:
- case PSCI_CPU_FREEZE:
- case PSCI_CPU_DEFAULT_SUSPEND:
- case PSCI_NODE_HW_STATE:
- case PSCI_SYSTEM_SUSPEND:
- case PSCI_SET_SYSPEND_MODE:
- case PSCI_STAT_RESIDENCY:
- case PSCI_STAT_COUNT:
- case PSCI_SYSTEM_RESET2:
- case PSCI_MEM_PROTECT:
- case PSCI_MEM_PROTECT_CHECK_RANGE:
- /* Block all other known PSCI calls. */
- *ret = PSCI_RETURN_NOT_SUPPORTED;
- break;
-
- default:
- return false;
- }
-
- return true;
-}
-
-/**
* Sets or clears the VI bit in the HCR_EL2 register saved in the given
* arch_regs.
*/
@@ -433,13 +238,9 @@
ret.new = NULL;
- if (current()->vm->id == HF_PRIMARY_VM_ID) {
- int32_t psci_ret;
-
- if (psci_handler(arg0, arg1, arg2, arg3, &psci_ret)) {
- ret.user_ret = psci_ret;
- return ret;
- }
+ if (psci_handler(current(), arg0, arg1, arg2, arg3, &ret.user_ret,
+ &ret.new)) {
+ return ret;
}
switch ((uint32_t)arg0) {
@@ -633,19 +434,20 @@
case 0x17: /* EC = 010111, SMC instruction. */ {
uintreg_t smc_pc = vcpu->regs.pc;
- int32_t ret;
+ uintreg_t ret;
+ struct vcpu *next = NULL;
- if (vcpu->vm->id != HF_PRIMARY_VM_ID ||
- !psci_handler(vcpu->regs.r[0], vcpu->regs.r[1],
- vcpu->regs.r[2], vcpu->regs.r[3], &ret)) {
+ if (!psci_handler(vcpu, vcpu->regs.r[0], vcpu->regs.r[1],
+ vcpu->regs.r[2], vcpu->regs.r[3], &ret,
+ &next)) {
dlog("Unsupported SMC call: 0x%x\n", vcpu->regs.r[0]);
- ret = PSCI_RETURN_NOT_SUPPORTED;
+ ret = PSCI_ERROR_NOT_SUPPORTED;
}
/* Skip the SMC instruction. */
vcpu->regs.pc = smc_pc + (esr & (1u << 25) ? 4 : 2);
vcpu->regs.r[0] = ret;
- return NULL;
+ return next;
}
default: