Implement minimal PSCI for secondary VMs to manage their vCPUs.
Bug: 132422393
Change-Id: I44643ec9eec722dfe0332b7ffefadcdd8dd98985
diff --git a/inc/hf/api.h b/inc/hf/api.h
index 259d59e..a0151c0 100644
--- a/inc/hf/api.h
+++ b/inc/hf/api.h
@@ -40,7 +40,9 @@
struct vcpu *api_preempt(struct vcpu *current);
struct vcpu *api_wait_for_interrupt(struct vcpu *current);
+struct vcpu *api_vcpu_off(struct vcpu *current);
struct vcpu *api_abort(struct vcpu *current);
+struct vcpu *api_wake_up(struct vcpu *current, struct vcpu *target_vcpu);
int64_t api_interrupt_enable(uint32_t intid, bool enable, struct vcpu *current);
uint32_t api_interrupt_get(struct vcpu *current);
diff --git a/inc/hf/arch/timer.h b/inc/hf/arch/timer.h
index f226020..f708f7b 100644
--- a/inc/hf/arch/timer.h
+++ b/inc/hf/arch/timer.h
@@ -58,6 +58,11 @@
bool arch_timer_enabled_current(void);
/**
+ * Disable the virtual timer for the currently active vCPU.
+ */
+void arch_timer_disable_current(void);
+
+/**
* Returns the number of ticks remaining on the virtual timer of the currently
* active vCPU, or 0 if it has already expired. This is undefined if the timer
* is not enabled.
diff --git a/inc/hf/cpu.h b/inc/hf/cpu.h
index 3c5db07..d0a5466 100644
--- a/inc/hf/cpu.h
+++ b/inc/hf/cpu.h
@@ -133,7 +133,8 @@
void vcpu_init(struct vcpu *vcpu, struct vm *vm);
void vcpu_on(struct vcpu_locked vcpu, ipaddr_t entry, uintreg_t arg);
size_t vcpu_index(const struct vcpu *vcpu);
-void vcpu_secondary_reset_and_start(struct vcpu *vcpu, ipaddr_t entry,
+bool vcpu_is_off(struct vcpu_locked vcpu);
+bool vcpu_secondary_reset_and_start(struct vcpu *vcpu, ipaddr_t entry,
uintreg_t arg);
bool vcpu_handle_page_fault(const struct vcpu *current,
diff --git a/src/api.c b/src/api.c
index c1f337c..438f6da 100644
--- a/src/api.c
+++ b/src/api.c
@@ -126,6 +126,24 @@
}
/**
+ * Puts the current vCPU in off mode, and returns to the primary VM.
+ */
+struct vcpu *api_vcpu_off(struct vcpu *current)
+{
+ struct hf_vcpu_run_return ret = {
+ .code = HF_VCPU_RUN_WAIT_FOR_INTERRUPT,
+ };
+
+ /*
+ * Disable the timer, so the scheduler doesn't get told to call back
+ * based on it.
+ */
+ arch_timer_disable_current();
+
+ return api_switch_to_primary(current, ret, VCPU_STATE_OFF);
+}
+
+/**
* Returns to the primary vm to allow this cpu to be used for other tasks as the
* vcpu does not have work to do at this moment. The current vcpu is marked as
* ready to be scheduled again. This SPCI function always returns SPCI_SUCCESS.
@@ -148,6 +166,20 @@
}
/**
+ * Switches to the primary so that it can switch to the target, or kick it if it
+ * is already running on a different physical CPU.
+ */
+struct vcpu *api_wake_up(struct vcpu *current, struct vcpu *target_vcpu)
+{
+ struct hf_vcpu_run_return ret = {
+ .code = HF_VCPU_RUN_WAKE_UP,
+ .wake_up.vm_id = target_vcpu->vm->id,
+ .wake_up.vcpu = vcpu_index(target_vcpu),
+ };
+ return api_switch_to_primary(current, ret, VCPU_STATE_READY);
+}
+
+/**
* Aborts the vCPU and triggers its VM to abort fully.
*/
struct vcpu *api_abort(struct vcpu *current)
@@ -258,7 +290,6 @@
uint32_t intid, struct vcpu *current,
struct vcpu **next)
{
- struct vm *target_vm = target_vcpu->vm;
uint32_t intid_index = intid / INTERRUPT_REGISTER_BITS;
uint32_t intid_mask = 1u << (intid % INTERRUPT_REGISTER_BITS);
int64_t ret = 0;
@@ -297,16 +328,7 @@
*/
ret = 1;
} else if (current != target_vcpu && next != NULL) {
- /*
- * Switch to the primary so that it can switch to the target, or
- * kick it if it is already running on a different physical CPU.
- */
- struct hf_vcpu_run_return ret = {
- .code = HF_VCPU_RUN_WAKE_UP,
- .wake_up.vm_id = target_vm->id,
- .wake_up.vcpu = target_vcpu - target_vm->vcpus,
- };
- *next = api_switch_to_primary(current, ret, VCPU_STATE_READY);
+ *next = api_wake_up(current, target_vcpu);
}
out:
diff --git a/src/arch/aarch64/hftest/power_mgmt.c b/src/arch/aarch64/hftest/power_mgmt.c
index 014c408..7970970 100644
--- a/src/arch/aarch64/hftest/power_mgmt.c
+++ b/src/arch/aarch64/hftest/power_mgmt.c
@@ -16,6 +16,7 @@
#include "hf/arch/vm/power_mgmt.h"
+#include "hf/assert.h"
#include "hf/spinlock.h"
#include "vmapi/hf/call.h"
@@ -96,6 +97,28 @@
}
}
+static_assert(POWER_STATUS_ON == PSCI_RETURN_ON,
+ "power_status enum values must match PSCI return values.");
+static_assert(POWER_STATUS_OFF == PSCI_RETURN_OFF,
+ "power_status enum values must match PSCI return values.");
+static_assert(POWER_STATUS_ON_PENDING == PSCI_RETURN_ON_PENDING,
+ "power_status enum values must match PSCI return values.");
+
+/**
+ * Returns the power status of the given CPU.
+ */
+enum power_status cpu_status(uint64_t cpu_id)
+{
+ uint32_t lowest_affinity_level = 0;
+
+ /*
+ * This works because the power_status enum values happen to be the same
+ * as the PSCI_RETURN_* values. The static_asserts above validate that
+ * this is the case.
+ */
+ return smc(PSCI_AFFINITY_INFO, cpu_id, lowest_affinity_level, 0);
+}
+
/**
* Shuts down the system or exits emulation.
*/
diff --git a/src/arch/aarch64/hypervisor/BUILD.gn b/src/arch/aarch64/hypervisor/BUILD.gn
index c98b888..984adbf 100644
--- a/src/arch/aarch64/hypervisor/BUILD.gn
+++ b/src/arch/aarch64/hypervisor/BUILD.gn
@@ -25,6 +25,7 @@
sources += [
"handler.c",
"offsets.c",
+ "psci_handler.c",
]
deps = [
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:
diff --git a/src/arch/aarch64/hypervisor/psci_handler.c b/src/arch/aarch64/hypervisor/psci_handler.c
new file mode 100644
index 0000000..76e4798
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/psci_handler.c
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "psci_handler.h"
+
+#include <stdint.h>
+
+#include "hf/arch/types.h"
+
+#include "hf/api.h"
+#include "hf/cpu.h"
+#include "hf/dlog.h"
+#include "hf/panic.h"
+#include "hf/vm.h"
+
+#include "psci.h"
+#include "smc.h"
+
+static uint32_t el3_psci_version;
+
+void cpu_entry(struct cpu *c);
+
+/* 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;
+ }
+}
+
+/**
+ * Handles PSCI requests received via HVC or SMC instructions from the primary
+ * VM.
+ *
+ * A minimal PSCI 1.1 interface is offered which can make use of the
+ * implementation of PSCI in EL3 by acting as an adapter.
+ *
+ * Returns true if the request was a PSCI one, false otherwise.
+ */
+bool psci_primary_vm_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
+ uintreg_t arg1, uintreg_t arg2, uintreg_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_ERROR_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_ERROR_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_ERROR_INVALID_PARAMETERS;
+ break;
+ }
+
+ if (arg1 != 0) {
+ *ret = PSCI_ERROR_NOT_SUPPORTED;
+ break;
+ }
+
+ sl_lock(&c->lock);
+ if (c->is_on) {
+ *ret = PSCI_RETURN_ON;
+ } else {
+ *ret = PSCI_RETURN_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.
+ */
+ 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(vcpu->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_ERROR_INVALID_PARAMETERS;
+ break;
+ }
+
+ if (cpu_on(c, ipa_init(arg1), arg2)) {
+ *ret = PSCI_ERROR_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_ERROR_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_ERROR_NOT_SUPPORTED;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Convert a PSCI CPU / affinity ID for a secondary VM to the corresponding vCPU
+ * index.
+ */
+uint32_t vcpu_id_to_index(uint64_t vcpu_id)
+{
+ /* For now we use indices as IDs for the purposes of PSCI. */
+ return vcpu_id;
+}
+
+/**
+ * Handles PSCI requests received via HVC or SMC instructions from a secondary
+ * VM.
+ *
+ * A minimal PSCI 1.1 interface is offered which can start and stop vCPUs in
+ * collaboration with the scheduler in the primary VM.
+ *
+ * Returns true if the request was a PSCI one, false otherwise.
+ */
+bool psci_secondary_vm_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
+ uintreg_t arg1, uintreg_t arg2, uintreg_t *ret,
+ struct vcpu **next)
+{
+ 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:
+ /*
+ * Does not offer OS-initiated mode but does use
+ * extended StateID Format.
+ */
+ *ret = 0x2;
+ break;
+
+ case PSCI_VERSION:
+ case PSCI_FEATURES:
+ 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_ERROR_NOT_SUPPORTED;
+ break;
+ }
+ break;
+
+ case PSCI_AFFINITY_INFO: {
+ uint64_t target_affinity = arg0;
+ uint32_t lowest_affinity_level = arg1;
+ struct vm *vm = vcpu->vm;
+ struct vcpu_locked target_vcpu;
+ uint32_t target_vcpu_index = vcpu_id_to_index(target_affinity);
+
+ if (lowest_affinity_level != 0) {
+ /* Affinity levels greater than 0 not supported. */
+ *ret = PSCI_ERROR_INVALID_PARAMETERS;
+ break;
+ }
+
+ if (target_vcpu_index >= vm->vcpu_count) {
+ *ret = PSCI_ERROR_INVALID_PARAMETERS;
+ break;
+ }
+
+ target_vcpu = vcpu_lock(vm_get_vcpu(vm, target_vcpu_index));
+ *ret = vcpu_is_off(target_vcpu) ? PSCI_RETURN_OFF
+ : PSCI_RETURN_ON;
+ vcpu_unlock(&target_vcpu);
+ break;
+ }
+
+ case PSCI_CPU_SUSPEND: {
+ /*
+ * Downgrade suspend request to WFI and return SUCCESS, as
+ * allowed by the specification.
+ */
+ *next = api_wait_for_interrupt(vcpu);
+ *ret = PSCI_RETURN_SUCCESS;
+ break;
+ }
+
+ case PSCI_CPU_OFF:
+ /*
+ * Should never return to the caller, but in case it somehow
+ * does.
+ */
+ *ret = PSCI_ERROR_DENIED;
+ /* Tell the scheduler not to run the vCPU again. */
+ *next = api_vcpu_off(vcpu);
+ break;
+
+ case PSCI_CPU_ON: {
+ /* Parameter names as per PSCI specification. */
+ uint64_t target_cpu = arg0;
+ ipaddr_t entry_point_address = ipa_init(arg1);
+ uint64_t context_id = arg2;
+ uint32_t target_vcpu_index = vcpu_id_to_index(target_cpu);
+ struct vm *vm = vcpu->vm;
+ struct vcpu *target_vcpu;
+
+ if (target_vcpu_index >= vm->vcpu_count) {
+ *ret = PSCI_ERROR_INVALID_PARAMETERS;
+ break;
+ }
+
+ target_vcpu = vm_get_vcpu(vm, target_vcpu_index);
+
+ if (vcpu_secondary_reset_and_start(
+ target_vcpu, entry_point_address, context_id)) {
+ /*
+ * Tell the scheduler that it can start running the new
+ * vCPU now.
+ */
+ *next = api_wake_up(vcpu, target_vcpu);
+ *ret = PSCI_RETURN_SUCCESS;
+ } else {
+ *ret = PSCI_ERROR_ALREADY_ON;
+ }
+
+ break;
+ }
+
+ case PSCI_SYSTEM_OFF:
+ case PSCI_SYSTEM_RESET:
+ 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_ERROR_NOT_SUPPORTED;
+ break;
+
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Handles PSCI requests received via HVC or SMC instructions from a VM.
+ * Requests from primary and secondary VMs are dealt with differently.
+ *
+ * Returns true if the request was a PSCI one, false otherwise.
+ */
+bool psci_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
+ uintreg_t arg1, uintreg_t arg2, uintreg_t *ret,
+ struct vcpu **next)
+{
+ if (vcpu->vm->id == HF_PRIMARY_VM_ID) {
+ return psci_primary_vm_handler(vcpu, func, arg0, arg1, arg2,
+ ret);
+ }
+ return psci_secondary_vm_handler(vcpu, func, arg0, arg1, arg2, ret,
+ next);
+}
diff --git a/src/arch/aarch64/hypervisor/psci_handler.h b/src/arch/aarch64/hypervisor/psci_handler.h
new file mode 100644
index 0000000..479c1cd
--- /dev/null
+++ b/src/arch/aarch64/hypervisor/psci_handler.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2018 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include "hf/arch/types.h"
+
+#include "hf/cpu.h"
+
+bool psci_handler(struct vcpu *vcpu, uint32_t func, uintreg_t arg0,
+ uintreg_t arg1, uintreg_t arg2, uintreg_t *ret,
+ struct vcpu **next);
diff --git a/src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h b/src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h
index dfd4198..956091f 100644
--- a/src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h
+++ b/src/arch/aarch64/inc/hf/arch/vm/power_mgmt.h
@@ -21,9 +21,16 @@
#include <stdint.h>
#include <stdnoreturn.h>
+enum power_status {
+ POWER_STATUS_ON,
+ POWER_STATUS_OFF,
+ POWER_STATUS_ON_PENDING,
+};
+
noreturn void arch_power_off(void);
bool cpu_start(uintptr_t id, void *stack, size_t stack_size,
void (*entry)(uintptr_t arg), uintptr_t arg);
noreturn void cpu_stop(void);
+enum power_status cpu_status(uint64_t cpu_id);
diff --git a/src/arch/aarch64/psci.h b/src/arch/aarch64/psci.h
index 041ac44..eef3fc0 100644
--- a/src/arch/aarch64/psci.h
+++ b/src/arch/aarch64/psci.h
@@ -39,7 +39,7 @@
* TODO: Trusted OS call: 0x32000000 - 0x3f000000
*/
-#define SMCCC_RETURN_UNKNOWN (-1)
+#define SMCCC_ERROR_UNKNOWN (-1)
/* The following are PSCI version codes. */
#define PSCI_VERSION_0_2 0x00000002
@@ -70,15 +70,18 @@
#define PSCI_MEM_PROTECT_CHECK_RANGE 0x84000014
/* The following are return codes for PSCI. */
+#define PSCI_RETURN_ON_PENDING 2
+#define PSCI_RETURN_OFF 1
+#define PSCI_RETURN_ON 0
#define PSCI_RETURN_SUCCESS 0
-#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)
-#define PSCI_RETURN_ON_PENDING (-5)
-#define PSCI_RETURN_INTERNAL_FAILURE (-6)
-#define PSCI_NOT_PRESENT (-7)
-#define PSCI_DISABLE (-8)
-#define PSCI_INVALID_ADDRESS (-9)
+#define PSCI_ERROR_NOT_SUPPORTED SMCCC_ERROR_UNKNOWN
+#define PSCI_ERROR_INVALID_PARAMETERS (-2)
+#define PSCI_ERROR_DENIED (-3)
+#define PSCI_ERROR_ALREADY_ON (-4)
+#define PSCI_ERROR_ON_PENDING (-5)
+#define PSCI_ERROR_INTERNAL_FAILURE (-6)
+#define PSCI_ERROR_NOT_PRESENT (-7)
+#define PSCI_ERROR_DISABLE (-8)
+#define PSCI_ERROR_INVALID_ADDRESS (-9)
/* clang-format on */
diff --git a/src/arch/aarch64/timer.c b/src/arch/aarch64/timer.c
index 12f4132..90b9ca8 100644
--- a/src/arch/aarch64/timer.c
+++ b/src/arch/aarch64/timer.c
@@ -129,6 +129,14 @@
}
/**
+ * Disables the virtual timer for the currently active vCPU.
+ */
+void arch_timer_disable_current(void)
+{
+ write_msr(cntv_ctl_el0, 0x0);
+}
+
+/**
* Returns the number of ticks remaining on the virtual timer of the currently
* active vCPU, or 0 if it has already expired. This is undefined if the timer
* is not enabled.
diff --git a/src/arch/fake/timer.c b/src/arch/fake/timer.c
index ba1294a..1c0d449 100644
--- a/src/arch/fake/timer.c
+++ b/src/arch/fake/timer.c
@@ -54,6 +54,11 @@
return false;
}
+void arch_timer_disable_current(void)
+{
+ /* TODO */
+}
+
uint64_t arch_timer_remaining_ns_current(void)
{
/* TODO */
diff --git a/src/cpu.c b/src/cpu.c
index e35b1ec..affba4a 100644
--- a/src/cpu.c
+++ b/src/cpu.c
@@ -209,27 +209,62 @@
}
/**
- * Starts a vCPU of a secondary VM.
+ * Check whether the given vcpu_state is an off state, for the purpose of
+ * turning vCPUs on and off. Note that aborted still counts as on in this
+ * context.
*/
-void vcpu_secondary_reset_and_start(struct vcpu *vcpu, ipaddr_t entry,
+bool vcpu_is_off(struct vcpu_locked vcpu)
+{
+ switch (vcpu.vcpu->state) {
+ case VCPU_STATE_OFF:
+ return true;
+ case VCPU_STATE_READY:
+ case VCPU_STATE_RUNNING:
+ case VCPU_STATE_BLOCKED_MAILBOX:
+ case VCPU_STATE_BLOCKED_INTERRUPT:
+ case VCPU_STATE_ABORTED:
+ /*
+ * Aborted still counts as ON for the purposes of PSCI,
+ * because according to the PSCI specification (section
+ * 5.7.1) a core is only considered to be off if it has
+ * been turned off with a CPU_OFF call or hasn't yet
+ * been turned on with a CPU_ON call.
+ */
+ return false;
+ }
+}
+
+/**
+ * Starts a vCPU of a secondary VM.
+ *
+ * Returns true if the secondary was reset and started, or false if it was
+ * already on and so nothing was done.
+ */
+bool vcpu_secondary_reset_and_start(struct vcpu *vcpu, ipaddr_t entry,
uintreg_t arg)
{
struct vcpu_locked vcpu_locked;
struct vm *vm = vcpu->vm;
+ bool vcpu_was_off;
assert(vm->id != HF_PRIMARY_VM_ID);
vcpu_locked = vcpu_lock(vcpu);
- /*
- * Set vCPU registers to a clean state ready for boot. As this is a
- * secondary which can migrate between pCPUs, the ID of the vCPU is
- * defined as the index and does not match the ID of the pCPU it is
- * running on.
- */
- arch_regs_reset(&vcpu->regs, false, vm->id, vcpu_index(vcpu),
- vm->ptable.root);
- vcpu_on(vcpu_locked, entry, arg);
+ vcpu_was_off = vcpu_is_off(vcpu_locked);
+ if (vcpu_was_off) {
+ /*
+ * Set vCPU registers to a clean state ready for boot. As this
+ * is a secondary which can migrate between pCPUs, the ID of the
+ * vCPU is defined as the index and does not match the ID of the
+ * pCPU it is running on.
+ */
+ arch_regs_reset(&vcpu->regs, false, vm->id, vcpu_index(vcpu),
+ vm->ptable.root);
+ vcpu_on(vcpu_locked, entry, arg);
+ }
vcpu_unlock(&vcpu_locked);
+
+ return vcpu_was_off;
}
/**
diff --git a/test/vmapi/primary_with_secondaries/BUILD.gn b/test/vmapi/primary_with_secondaries/BUILD.gn
index 15b190c..d24570f 100644
--- a/test/vmapi/primary_with_secondaries/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/BUILD.gn
@@ -32,6 +32,7 @@
"memory_sharing.c",
"no_services.c",
"run_race.c",
+ "smp.c",
"spci.c",
]
@@ -60,5 +61,11 @@
"services1",
"services:service_vm1",
],
+ [
+ "1048576",
+ "2",
+ "services2",
+ "services:service_vm2",
+ ],
]
}
diff --git a/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h b/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h
index b2498f7..a8ff882 100644
--- a/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h
+++ b/test/vmapi/primary_with_secondaries/inc/primary_with_secondary.h
@@ -18,6 +18,7 @@
#define SERVICE_VM0 1
#define SERVICE_VM1 2
+#define SERVICE_VM2 3
#define SELF_INTERRUPT_ID 5
#define EXTERNAL_INTERRUPT_ID_A 7
diff --git a/test/vmapi/primary_with_secondaries/no_services.c b/test/vmapi/primary_with_secondaries/no_services.c
index 98687ec..9f190e0 100644
--- a/test/vmapi/primary_with_secondaries/no_services.c
+++ b/test/vmapi/primary_with_secondaries/no_services.c
@@ -43,11 +43,11 @@
}
/**
- * Confirm there are 2 secondary VMs as well as this primary VM.
+ * Confirm there are 3 secondary VMs as well as this primary VM.
*/
-TEST(hf_vm_get_count, four_secondary_vms)
+TEST(hf_vm_get_count, three_secondary_vms)
{
- EXPECT_EQ(hf_vm_get_count(), 3);
+ EXPECT_EQ(hf_vm_get_count(), 4);
}
/**
diff --git a/test/vmapi/primary_with_secondaries/services/BUILD.gn b/test/vmapi/primary_with_secondaries/services/BUILD.gn
index e8382bf..073494d 100644
--- a/test/vmapi/primary_with_secondaries/services/BUILD.gn
+++ b/test/vmapi/primary_with_secondaries/services/BUILD.gn
@@ -140,6 +140,18 @@
]
}
+# Service to start a second vCPU and send messages from both.
+source_set("smp") {
+ testonly = true
+ public_configs = [
+ "..:config",
+ "//test/hftest:hftest_config",
+ ]
+ sources = [
+ "smp.c",
+ ]
+}
+
# Service to check that WFI is a no-op when there are pending interrupts.
source_set("wfi") {
testonly = true
@@ -195,3 +207,12 @@
"//test/hftest:hftest_secondary_vm",
]
}
+
+vm_kernel("service_vm2") {
+ testonly = true
+
+ deps = [
+ ":smp",
+ "//test/hftest:hftest_secondary_vm",
+ ]
+}
diff --git a/test/vmapi/primary_with_secondaries/services/smp.c b/test/vmapi/primary_with_secondaries/services/smp.c
new file mode 100644
index 0000000..863919d
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/services/smp.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdalign.h>
+#include <stdint.h>
+
+#include "hf/arch/cpu.h"
+#include "hf/arch/vm/power_mgmt.h"
+
+#include "hf/dlog.h"
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+#include "vmapi/hf/spci.h"
+
+#include "../psci.h"
+#include "hftest.h"
+#include "primary_with_secondary.h"
+
+#define ARG_VALUE 42
+
+/*
+ * Secondary VM that starts a second vCPU and then sends messages from both.
+ */
+
+alignas(4096) static char stack[4096];
+
+/** Send a message back to the primary. */
+void send_message(const char *message, uint32_t size)
+{
+ memcpy_s(SERVICE_SEND_BUFFER()->payload, SPCI_MSG_PAYLOAD_MAX, message,
+ size);
+ spci_message_init(SERVICE_SEND_BUFFER(), size, HF_PRIMARY_VM_ID,
+ hf_vm_get_id());
+
+ ASSERT_EQ(spci_msg_send(0), SPCI_SUCCESS);
+}
+
+/**
+ * Entry point of the second vCPU.
+ */
+static void vm_cpu_entry(uintptr_t arg)
+{
+ ASSERT_EQ(arg, ARG_VALUE);
+
+ /* Check that vCPU statuses are as expected. */
+ ASSERT_EQ(cpu_status(0), POWER_STATUS_ON);
+ ASSERT_EQ(cpu_status(1), POWER_STATUS_ON);
+
+ dlog("Secondary second vCPU started.\n");
+ send_message("vCPU 1", sizeof("vCPU 1"));
+ dlog("Secondary second vCPU finishing\n");
+}
+
+TEST_SERVICE(smp)
+{
+ /* Check that vCPU statuses are as expected. */
+ ASSERT_EQ(cpu_status(0), POWER_STATUS_ON);
+ ASSERT_EQ(cpu_status(1), POWER_STATUS_OFF);
+
+ /* Start second vCPU. */
+ dlog("Secondary starting second vCPU.\n");
+ ASSERT_TRUE(
+ cpu_start(1, stack, sizeof(stack), vm_cpu_entry, ARG_VALUE));
+ dlog("Secondary started second vCPU.\n");
+
+ /* Check that vCPU statuses are as expected. */
+ ASSERT_EQ(cpu_status(0), POWER_STATUS_ON);
+ ASSERT_EQ(cpu_status(1), POWER_STATUS_ON);
+
+ send_message("vCPU 0", sizeof("vCPU 0"));
+}
diff --git a/test/vmapi/primary_with_secondaries/smp.c b/test/vmapi/primary_with_secondaries/smp.c
new file mode 100644
index 0000000..64e0739
--- /dev/null
+++ b/test/vmapi/primary_with_secondaries/smp.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <stdint.h>
+
+#include "hf/std.h"
+
+#include "vmapi/hf/call.h"
+
+#include "hftest.h"
+#include "primary_with_secondary.h"
+#include "util.h"
+
+/**
+ * Run a service that starts a second vCPU, and check that both the first and
+ * second vCPU send messages to us.
+ */
+TEST(smp, two_vcpus)
+{
+ const char expected_response_0[] = "vCPU 0";
+ const char expected_response_1[] = "vCPU 1";
+ struct hf_vcpu_run_return run_res;
+ struct mailbox_buffers mb = set_up_mailbox();
+
+ SERVICE_SELECT(SERVICE_VM2, "smp", mb.send);
+
+ /* Let the first vCPU start the second vCPU. */
+ run_res = hf_vcpu_run(SERVICE_VM2, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAKE_UP);
+ EXPECT_EQ(run_res.wake_up.vm_id, SERVICE_VM2);
+ EXPECT_EQ(run_res.wake_up.vcpu, 1);
+
+ /* Run the second vCPU and wait for a message. */
+ dlog("Run second vCPU for message\n");
+ run_res = hf_vcpu_run(SERVICE_VM2, 1);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(mb.recv->length, sizeof(expected_response_1));
+ EXPECT_EQ(memcmp(mb.recv->payload, expected_response_1,
+ sizeof(expected_response_1)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+
+ /* Run the first vCPU and wait for a different message. */
+ dlog("Run first vCPU for message\n");
+ run_res = hf_vcpu_run(SERVICE_VM2, 0);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_MESSAGE);
+ EXPECT_EQ(mb.recv->length, sizeof(expected_response_0));
+ EXPECT_EQ(memcmp(mb.recv->payload, expected_response_0,
+ sizeof(expected_response_0)),
+ 0);
+ EXPECT_EQ(hf_mailbox_clear(), 0);
+
+ /* Run the second vCPU again, and expect it to turn itself off. */
+ dlog("Run second vCPU for poweroff.\n");
+ run_res = hf_vcpu_run(SERVICE_VM2, 1);
+ EXPECT_EQ(run_res.code, HF_VCPU_RUN_WAIT_FOR_INTERRUPT);
+ EXPECT_EQ(run_res.sleep.ns, HF_SLEEP_INDEFINITE);
+}