Implement minimal PSCI 1.1.

Only the mandatory functions are supported. This required adding
PSCI_FEATURES which is used to discover which PSCI features are
available and PSCI_CPU_SUSPEND which can put CPUs into low power states.
The hypervisor acts as an adapter for lower version of PSCI running in
EL3.

When waking from a powered down suspend, the CPU needs to be reset to
the same state as when it is first turned on. Only the entry point and
context argument should be present.

Change-Id: I088f214258a1cdd429bee63f1846603057769217
diff --git a/src/arch/aarch64/cpu.c b/src/arch/aarch64/cpu.c
index c13bbf3..1f8ac31 100644
--- a/src/arch/aarch64/cpu.c
+++ b/src/arch/aarch64/cpu.c
@@ -21,6 +21,7 @@
 #include <stdint.h>
 
 #include "hf/addr.h"
+#include "hf/std.h"
 
 void arch_irq_disable(void)
 {
@@ -32,13 +33,20 @@
 	__asm__ volatile("msr DAIFClr, #0xf");
 }
 
-void arch_regs_init(struct arch_regs *r, bool is_primary, uint64_t vmid,
-		    paddr_t table, uint32_t index)
+void arch_regs_reset(struct arch_regs *r, bool is_primary, uint64_t vmid,
+		     paddr_t table, uint32_t index)
 {
+	uintreg_t pc = r->pc;
+	uintreg_t arg = r->r[0];
 	uintreg_t hcr;
 	uintreg_t cptr;
 	uintreg_t cnthctl;
 
+	memset(r, 0, sizeof(*r));
+
+	r->pc = pc;
+	r->r[0] = arg;
+
 	/* TODO: Determine if we need to set TSW. */
 	hcr = (1u << 31) | /* RW bit. */
 	      (1u << 21) | /* TACR, trap access to ACTRL_EL1. */
diff --git a/src/arch/aarch64/handler.c b/src/arch/aarch64/handler.c
index 1ceb04d..a4246fe 100644
--- a/src/arch/aarch64/handler.c
+++ b/src/arch/aarch64/handler.c
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
+#include <stdnoreturn.h>
+
+#include "hf/arch/init.h"
+
 #include "hf/api.h"
 #include "hf/cpu.h"
 #include "hf/dlog.h"
@@ -34,7 +38,32 @@
 
 void cpu_entry(struct cpu *c);
 
-static inline struct vcpu *current(void)
+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)
 {
 	return (struct vcpu *)read_msr(tpidr_el2);
 }
@@ -94,40 +123,45 @@
 	}
 }
 
-void irq_current_exception(uintreg_t elr, uintreg_t spsr)
+/**
+ * This should never be reached as it means something has gone very wrong.
+ */
+static noreturn void hang(void)
+{
+	dlog("Hang: something went very wrong!\n");
+	for (;;) {
+		/* Do nothing. */
+	}
+}
+
+noreturn void irq_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	(void)elr;
 	(void)spsr;
 
 	dlog("IRQ from current\n");
-	for (;;) {
-		/* do nothing */
-	}
+	hang();
 }
 
-void fiq_current_exception(uintreg_t elr, uintreg_t spsr)
+noreturn void fiq_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	(void)elr;
 	(void)spsr;
 
 	dlog("FIQ from current\n");
-	for (;;) {
-		/* do nothing */
-	}
+	hang();
 }
 
-void serr_current_exception(uintreg_t elr, uintreg_t spsr)
+noreturn void serr_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	(void)elr;
 	(void)spsr;
 
 	dlog("SERR from current\n");
-	for (;;) {
-		/* do nothing */
-	}
+	hang();
 }
 
-void sync_current_exception(uintreg_t elr, uintreg_t spsr)
+noreturn void sync_current_exception(uintreg_t elr, uintreg_t spsr)
 {
 	uintreg_t esr = read_msr(esr_el2);
 
@@ -150,46 +184,86 @@
 		dlog("Unknown current sync exception pc=0x%x, esr=0x%x, "
 		     "ec=0x%x\n",
 		     elr, esr, esr >> 26);
+		break;
 	}
 
-	for (;;) {
-		/* do nothing */
-	}
+	hang();
 }
 
 /**
  * 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;
-	int32_t sret;
 
-	switch (func & ~PSCI_CONVENTION_MASK) {
+	/*
+	 * 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:
-		/* Version is 0.2. */
-		*ret = 2;
+		*ret = PSCI_VERSION_1_1;
 		break;
 
-	case PSCI_MIGRATE_INFO_TYPE:
-		/* Trusted OS does not require migration. */
-		*ret = 2;
+	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);
-		for (;;) {
-		}
+		hang();
 		break;
 
 	case PSCI_SYSTEM_RESET:
 		smc(PSCI_SYSTEM_RESET, 0, 0, 0);
-		for (;;) {
-		}
+		hang();
 		break;
 
 	case PSCI_AFFINITY_INFO:
@@ -213,11 +287,25 @@
 		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);
-		for (;;) {
-		}
+		hang();
 		break;
 
 	case PSCI_CPU_ON:
@@ -239,19 +327,32 @@
 		 * itself off).
 		 */
 		do {
-			sret = smc(PSCI_CPU_ON, arg0, (uintreg_t)&cpu_entry,
-				   (uintreg_t)c);
-		} while (sret == PSCI_RETURN_ALREADY_ON);
+			*ret = smc(PSCI_CPU_ON | SMCCC_64_BIT, arg0,
+				   (uintreg_t)&cpu_entry, (uintreg_t)c);
+		} while (*ret == PSCI_RETURN_ALREADY_ON);
 
-		if (sret == PSCI_RETURN_SUCCESS) {
-			*ret = PSCI_RETURN_SUCCESS;
-		} else {
-			dlog("Unexpected return from PSCI_CPU_ON: 0x%x\n",
-			     sret);
-			*ret = PSCI_RETURN_INTERNAL_FAILURE;
+		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;
 	}
@@ -303,7 +404,7 @@
 		}
 	}
 
-	switch ((uint32_t)arg0 & ~PSCI_CONVENTION_MASK) {
+	switch ((uint32_t)arg0 & ~SMCCC_CONVENTION_MASK) {
 	case HF_VM_GET_ID:
 		ret.user_ret = api_vm_get_id(current());
 		break;
@@ -458,7 +559,6 @@
 struct vcpu *sync_lower_exception(uintreg_t esr)
 {
 	struct vcpu *vcpu = current();
-	int32_t ret;
 	struct vcpu_fault_info info;
 
 	switch (esr >> 26) {
@@ -498,18 +598,22 @@
 		}
 		break;
 
-	case 0x17: /* EC = 010111, SMC instruction. */
+	case 0x17: /* EC = 010111, SMC instruction. */ {
+		uintreg_t smc_pc = vcpu->regs.pc;
+		int32_t ret;
+
 		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)) {
 			dlog("Unsupported SMC call: 0x%x\n", vcpu->regs.r[0]);
-			ret = -1;
+			ret = PSCI_RETURN_NOT_SUPPORTED;
 		}
 
 		/* Skip the SMC instruction. */
-		vcpu->regs.pc += (esr & (1u << 25)) ? 4 : 2;
+		vcpu->regs.pc = smc_pc + (esr & (1u << 25) ? 4 : 2);
 		vcpu->regs.r[0] = ret;
 		return NULL;
+	}
 
 	default:
 		dlog("Unknown lower sync exception pc=0x%x, esr=0x%x, "
diff --git a/src/arch/aarch64/psci.h b/src/arch/aarch64/psci.h
index 1ed21ce..3e3d765 100644
--- a/src/arch/aarch64/psci.h
+++ b/src/arch/aarch64/psci.h
@@ -18,7 +18,33 @@
 
 /* clang-format off */
 
-#define PSCI_CONVENTION_MASK (1u << 30)
+#define SMCCC_CALL_TYPE_MASK  0x80000000
+#define SMCCC_YIELDING_CALL   0x00000000
+#define SMCCC_FAST_CALL       0x80000000
+
+#define SMCCC_CONVENTION_MASK 0x40000000
+#define SMCCC_32_BIT          0x00000000
+#define SMCCC_64_BIT          0x40000000
+
+#define SMCCC_SERVICE_CALL_MASK                0x3f000000
+#define SMCCC_ARM_ARCHITECTURE_CALL            0x00000000
+#define SMCCC_CPU_SERVICE_CALL                 0x01000000
+#define SMCCC_SIP_SERVICE_CALL                 0x02000000
+#define SMCCC_OEM_SERVICE_CALL                 0x03000000
+#define SMCCC_STANDARD_SECURE_SERVICE_CALL     0x04000000
+#define SMCCC_STANDARD_HYPERVISOR_SERVICE_CALL 0x05000000
+#define SMCCC_VENDOR_HYPERVISOR_SERVICE_CALL   0x06000000
+/*
+ * TODO: Trusted application call: 0x30000000 - 0x31000000
+ * TODO: Trusted OS call: 0x32000000 - 0x3f000000
+ */
+
+#define SMCCC_RETURN_UNKNOWN  (-1)
+
+/* The following are PSCI version codes. */
+#define PSCI_VERSION_0_2 0x00000002
+#define PSCI_VERSION_1_0 0x00010000
+#define PSCI_VERSION_1_1 0x00010001
 
 /* The following are function identifiers for PSCI. */
 #define PSCI_VERSION                 0x84000000
@@ -45,7 +71,7 @@
 
 /* The following are return codes for PSCI. */
 #define PSCI_RETURN_SUCCESS            0
-#define PSCI_RETURN_NOT_SUPPORTED      (-1)
+#define PSCI_RETURN_NOT_SUPPORTED      SMCCC_RETURN_UNKNOWN
 #define PSCI_RETURN_INVALID_PARAMETERS (-2)
 #define PSCI_RETURN_DENIED             (-3)
 #define PSCI_RETURN_ALREADY_ON         (-4)
diff --git a/src/arch/fake/cpu.c b/src/arch/fake/cpu.c
index ab400d2..a00e940 100644
--- a/src/arch/fake/cpu.c
+++ b/src/arch/fake/cpu.c
@@ -26,8 +26,8 @@
 	/* TODO */
 }
 
-void arch_regs_init(struct arch_regs *r, bool is_primary, uint64_t vmid,
-		    paddr_t table, uint32_t index)
+void arch_regs_reset(struct arch_regs *r, bool is_primary, uint64_t vmid,
+		     paddr_t table, uint32_t index)
 {
 	/* TODO */
 	(void)is_primary;
diff --git a/src/cpu.c b/src/cpu.c
index 5db709c..2c58599 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -98,8 +98,7 @@
 		struct vm *vm = vm_get(HF_PRIMARY_VM_ID);
 		struct vcpu *vcpu = &vm->vcpus[cpu_index(c)];
 
-		arch_regs_set_pc_arg(&vcpu->regs, entry, arg);
-		vcpu_on(vcpu);
+		vcpu_on(vcpu, entry, arg);
 	}
 
 	return prev;
@@ -138,12 +137,12 @@
 	vcpu->regs_available = true;
 	vcpu->vm = vm;
 	vcpu->state = vcpu_state_off;
-	arch_regs_init(&vcpu->regs, vm->id == HF_PRIMARY_VM_ID, vm->id,
-		       vm->ptable.root, vcpu_index(vcpu));
 }
 
-void vcpu_on(struct vcpu *vcpu)
+void vcpu_on(struct vcpu *vcpu, ipaddr_t entry, uintreg_t arg)
 {
+	arch_regs_set_pc_arg(&vcpu->regs, entry, arg);
+
 	sl_lock(&vcpu->lock);
 	vcpu->state = vcpu_state_ready;
 	sl_unlock(&vcpu->lock);
diff --git a/src/load.c b/src/load.c
index 60b4ef4..11e5568 100644
--- a/src/load.c
+++ b/src/load.c
@@ -157,7 +157,7 @@
 			return false;
 		}
 
-		vm_start_vcpu(vm, 0, ipa_from_pa(primary_begin), kernel_arg);
+		vcpu_on(&vm->vcpus[0], ipa_from_pa(primary_begin), kernel_arg);
 	}
 
 	return true;
diff --git a/src/main.c b/src/main.c
index a88e67a..969cacf 100644
--- a/src/main.c
+++ b/src/main.c
@@ -18,6 +18,8 @@
 #include <stddef.h>
 #include <stdnoreturn.h>
 
+#include "hf/arch/init.h"
+
 #include "hf/api.h"
 #include "hf/boot_params.h"
 #include "hf/cpio.h"
@@ -73,6 +75,8 @@
 
 	dlog("Initialising hafnium\n");
 
+	arch_one_time_init();
+
 	mpool_init(&ppool, sizeof(struct mm_page_table));
 	mpool_add_chunk(&ppool, ptable_buf, sizeof(ptable_buf));
 
@@ -148,6 +152,7 @@
 struct vcpu *cpu_main(struct cpu *c)
 {
 	struct vcpu *vcpu;
+	struct vm *vm;
 
 	/*
 	 * Do global one-time initialisation just once. We avoid using atomics
@@ -160,13 +165,17 @@
 		one_time_init();
 	}
 
-	dlog("Starting up cpu %d\n", cpu_index(c));
-
 	if (!mm_cpu_init()) {
 		panic("mm_cpu_init failed");
 	}
 
 	vcpu = &vm_get(HF_PRIMARY_VM_ID)->vcpus[cpu_index(c)];
+	vm = vcpu->vm;
 	vcpu->cpu = c;
+
+	/* Reset the registers to give a clean start for the primary's vCPU. */
+	arch_regs_reset(&vcpu->regs, true, vm->id, vm->ptable.root,
+			vcpu_index(vcpu));
+
 	return vcpu;
 }
diff --git a/src/vm.c b/src/vm.c
index a670753..15e7521 100644
--- a/src/vm.c
+++ b/src/vm.c
@@ -109,7 +109,8 @@
 	struct vcpu *vcpu = &vm->vcpus[index];
 
 	if (index < vm->vcpu_count) {
-		arch_regs_set_pc_arg(&vcpu->regs, entry, arg);
-		vcpu_on(vcpu);
+		vcpu_on(vcpu, entry, arg);
+		arch_regs_reset(&vcpu->regs, vm->id == HF_PRIMARY_VM_ID, vm->id,
+				vm->ptable.root, vcpu_index(vcpu));
 	}
 }