@@ -7,5 +7,6 @@ obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o
obj-y += translate.o op_helper.o helper.o cpu.o
obj-y += neon_helper.o iwmmxt_helper.o
obj-y += gdbstub.o
+obj-$(CONFIG_SOFTMMU) += psci.o
obj-$(TARGET_AARCH64) += cpu64.o translate-a64.o helper-a64.o gdbstub64.o
obj-y += crypto_helper.o
@@ -101,6 +101,11 @@ typedef struct ARMCPU {
/* CPU currently in PSCI powered-off state */
bool powered_off;
+ /* PSCI emulation state
+ * 0 - disabled, 1 - smc, 2 - hvc
+ */
+ uint32_t psci_method;
+
/* [QEMU_]KVM_ARM_TARGET_* constant for this CPU, or
* QEMU_KVM_ARM_TARGET_NONE if the kernel doesn't support this CPU type.
*/
@@ -192,6 +197,7 @@ extern const struct VMStateDescription vmstate_arm_cpu;
void register_cp_regs_for_features(ARMCPU *cpu);
void init_cpreg_list(ARMCPU *cpu);
+bool arm_handle_psci(CPUState *cs);
bool arm_cpu_do_hvc(CPUState *cs);
bool arm_cpu_do_smc(CPUState *cs);
@@ -268,9 +268,12 @@ static void arm_cpu_initfn(Object *obj)
cpu->psci_version = 1; /* By default assume PSCI v0.1 */
cpu->kvm_target = QEMU_KVM_ARM_TARGET_NONE;
- if (tcg_enabled() && !inited) {
- inited = true;
- arm_translate_init();
+ if (tcg_enabled()) {
+ cpu->psci_version = 2; /* TCG implements PSCI 0.2 */
+ if (!inited) {
+ inited = true;
+ arm_translate_init();
+ }
}
}
@@ -1024,6 +1027,7 @@ static const ARMCPUInfo arm_cpus[] = {
static Property arm_cpu_properties[] = {
DEFINE_PROP_BOOL("start-powered-off", ARMCPU, start_powered_off, false),
+ DEFINE_PROP_UINT32("psci-method", ARMCPU, psci_method, 0),
DEFINE_PROP_UINT32("midr", ARMCPU, midr, 0),
DEFINE_PROP_END_OF_LIST()
};
@@ -1350,4 +1350,10 @@ static inline void cpu_pc_from_tb(CPUARMState *env, TranslationBlock *tb)
}
}
+enum {
+ QEMU_PSCI_METHOD_DISABLED = 0,
+ QEMU_PSCI_METHOD_SMC = 1,
+ QEMU_PSCI_METHOD_HVC = 2,
+};
+
#endif
@@ -3499,11 +3499,23 @@ void arm_v7m_cpu_do_interrupt(CPUState *cs)
bool arm_cpu_do_hvc(CPUState *cs)
{
+ ARMCPU *cpu = ARM_CPU(cs);
+
+ if (cpu->psci_method == QEMU_PSCI_METHOD_HVC) {
+ return arm_handle_psci(cs);
+ }
+
return false;
}
bool arm_cpu_do_smc(CPUState *cs)
{
+ ARMCPU *cpu = ARM_CPU(cs);
+
+ if (cpu->psci_method == QEMU_PSCI_METHOD_SMC) {
+ return arm_handle_psci(cs);
+ }
+
return false;
}
new file mode 100644
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 - Linaro
+ * Author: Rob Herring <rob.herring@linaro.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+#include <cpu.h>
+#include <cpu-qom.h>
+#include <kvm-consts.h>
+#include <sysemu/sysemu.h>
+#include <linux/psci.h>
+
+bool arm_handle_psci(CPUState *cs)
+{
+ /*
+ * This function partially implements the logic for dispatching Power State
+ * Coordination Interface (PSCI) calls (as described in ARM DEN 0022B.b),
+ * to the extent required for bringing up and taking down secondary cores,
+ * and for handling reset and poweroff requests.
+ * Additional information about the calling convention used is available in
+ * the document 'SMC Calling Convention' (ARM DEN 0028)
+ */
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+ uint64_t param[4];
+ uint64_t context_id, mpidr;
+ target_ulong entry;
+ int32_t ret = 0;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ /*
+ * All PSCI functions take explicit 32-bit or native int sized
+ * arguments so we can simply zero-extend all arguments regardless
+ * of which exact function we are about to call.
+ */
+ param[i] = is_a64(env) ? env->xregs[i] : env->regs[i];
+ }
+
+ if ((param[0] & PSCI_0_2_64BIT) && !is_a64(env)) {
+ ret = PSCI_RET_INVALID_PARAMS;
+ goto err;
+ }
+
+ switch (param[0]) {
+ CPUState *target_cpu_state;
+ ARMCPU *target_cpu;
+ CPUClass *target_cpu_class;
+
+ case PSCI_0_2_FN_PSCI_VERSION:
+ ret = PSCI_VERSION_MAJOR(0) | PSCI_VERSION_MINOR(2);
+ break;
+ case PSCI_0_2_FN_MIGRATE_INFO_TYPE:
+ ret = PSCI_0_2_TOS_MP; /* No trusted OS */
+ break;
+ case PSCI_0_2_FN_AFFINITY_INFO:
+ case PSCI_0_2_FN64_AFFINITY_INFO:
+ mpidr = param[1];
+
+ switch (param[2]) {
+ case 0:
+ target_cpu_state = qemu_get_cpu(mpidr & 0xff);
+ if (!target_cpu_state) {
+ ret = PSCI_RET_INVALID_PARAMS;
+ break;
+ }
+ target_cpu = ARM_CPU(target_cpu_state);
+ ret = target_cpu->powered_off ? 1 : 0;
+ break;
+ default:
+ /* Everything above affinity level 0 is always on. */
+ ret = 0;
+ }
+ break;
+ case PSCI_0_2_FN_SYSTEM_RESET:
+ qemu_system_reset_request();
+ break;
+ case PSCI_0_2_FN_SYSTEM_OFF:
+ qemu_system_shutdown_request();
+ break;
+ case QEMU_PSCI_0_1_FN_CPU_ON:
+ case PSCI_0_2_FN_CPU_ON:
+ case PSCI_0_2_FN64_CPU_ON:
+ mpidr = param[1];
+ entry = param[2];
+ context_id = param[3];
+
+ /* change to the cpu we are powering up */
+ target_cpu_state = qemu_get_cpu(mpidr & 0xff);
+ if (!target_cpu_state) {
+ ret = PSCI_RET_INVALID_PARAMS;
+ break;
+ }
+ target_cpu = ARM_CPU(target_cpu_state);
+ if (!target_cpu->powered_off) {
+ ret = PSCI_RET_ALREADY_ON;
+ break;
+ }
+ target_cpu_class = CPU_GET_CLASS(target_cpu);
+
+ /* Initialize the cpu we are turning on */
+ cpu_reset(target_cpu_state);
+ target_cpu_class->set_pc(target_cpu_state, entry);
+ target_cpu->powered_off = false;
+ target_cpu_state->interrupt_request |= CPU_INTERRUPT_EXITTB;
+
+ /*
+ * The PSCI spec mandates that newly brought up CPUs enter the
+ * exception level of the caller in the same execution mode as
+ * the caller, with context_id in x0/r0, respectively.
+ */
+ if (is_a64(env)) {
+ target_cpu->env.aarch64 = 1;
+ target_cpu->env.xregs[0] = context_id;
+ } else {
+ target_cpu->env.aarch64 = 0;
+ target_cpu->env.regs[0] = context_id;
+ }
+
+ ret = 0;
+ break;
+ case QEMU_PSCI_0_1_FN_CPU_OFF:
+ case PSCI_0_2_FN_CPU_OFF:
+ cpu->powered_off = true;
+ cs->exit_request = 1;
+ cs->halted = 1;
+
+ /* CPU_OFF should never return, but if it does return an error */
+ ret = PSCI_RET_DENIED;
+ break;
+ case QEMU_PSCI_0_1_FN_CPU_SUSPEND:
+ case PSCI_0_2_FN_CPU_SUSPEND:
+ case PSCI_0_2_FN64_CPU_SUSPEND:
+ /* Affinity levels are not supported in QEMU */
+ if (param[1] & 0xfffe0000) {
+ ret = PSCI_RET_INVALID_PARAMS;
+ break;
+ }
+ /* Powerdown is not supported, we always go into WFI */
+ cs->halted = 1;
+ cs->exit_request = 1;
+
+ /* Return success when we wakeup */
+ ret = 0;
+ break;
+ case QEMU_PSCI_0_1_FN_MIGRATE:
+ case PSCI_0_2_FN_MIGRATE:
+ ret = PSCI_RET_NOT_SUPPORTED;
+ break;
+ default:
+ return false;
+ }
+
+err:
+ if (is_a64(env)) {
+ env->xregs[0] = ret;
+ } else {
+ env->regs[0] = ret;
+ }
+ return true;
+}