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: