If KVM supports it, handle PSCI calls in QEMU. For CPU_ON and CPU_OFF, the arm-powerctl implementation used by TCG can be reused as is.
Note that we add some infrastructure to halt CPUs within QEMU rather than KVM (kvm_arch_process_async_events()) for reference, but it can be removed. To implement CPU_SUSPEND we do rely on halting in kernel, so we have to keep kvm_halt_in_kernel_allowed, set by kvm_irqchip_create(). As a result OFF CPUs are still parked in the kernel rather than in QEMU.
Signed-off-by: Jean-Philippe Brucker jean-philippe@linaro.org --- target/arm/internals.h | 2 +- target/arm/kvm.c | 49 +++++++++++++++++++++++++++++++++++++++++- target/arm/kvm64.c | 5 ++--- 3 files changed, 51 insertions(+), 5 deletions(-)
diff --git a/target/arm/internals.h b/target/arm/internals.h index 32821f8b04a..71df4ad3104 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -293,7 +293,7 @@ vaddr arm_adjust_watchpoint_address(CPUState *cs, vaddr addr, int len); /* Callback function for when a watchpoint or breakpoint triggers. */ void arm_debug_excp_handler(CPUState *cs);
-#if defined(CONFIG_USER_ONLY) || !defined(CONFIG_TCG) +#if defined(CONFIG_USER_ONLY) || (!defined(CONFIG_TCG) && !defined(CONFIG_KVM)) static inline bool arm_is_psci_call(ARMCPU *cpu, int excp_type) { return false; diff --git a/target/arm/kvm.c b/target/arm/kvm.c index 1a7e52b6bad..0fef482878b 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -276,6 +276,19 @@ int kvm_arch_init(MachineState *ms, KVMState *s) } }
+ /* + * To handle PSCI calls in QEMU, we need KVM support for suspending the + * vCPU, and of course the PSCI-to-userspace capability. + */ + if (cap_has_mp_halted && + kvm_check_extension(kvm_state, KVM_CAP_ARM_HVC_TO_USER) && + kvm_check_extension(kvm_state, KVM_CAP_ARM_PSCI_TO_USER)) { + if (kvm_vm_enable_cap(s, KVM_CAP_ARM_HVC_TO_USER, 0) || + kvm_vm_enable_cap(s, KVM_CAP_ARM_PSCI_TO_USER, 0)) { + error_report("Failed to enable KVM_CAP_ARM_PSCI_TO_USER"); + } + } + return ret; }
@@ -951,6 +964,32 @@ static int kvm_arm_handle_dabt_nisv(CPUState *cs, uint64_t esr_iss, return -1; }
+static int kvm_arm_handle_hypercall(CPUState *cs, uint16_t imm) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + + if (imm != 0) { + return 0; + } + + kvm_cpu_synchronize_state(cs); + + /* Under KVM, the PSCI conduit is HVC */ + cs->exception_index = EXCP_HVC; + env->exception.target_el = 1; + env->exception.syndrome = syn_aa64_hvc(imm); + qemu_mutex_lock_iothread(); + arm_cpu_do_interrupt(cs); + qemu_mutex_unlock_iothread(); + + /* + * For PSCI, exit the kvm_run loop and process the work. Especially + * important if this was a CPU_OFF command and we can't return to the guest. + */ + return EXCP_INTERRUPT; +} + int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) { int ret = 0; @@ -966,6 +1005,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) ret = kvm_arm_handle_dabt_nisv(cs, run->arm_nisv.esr_iss, run->arm_nisv.fault_ipa); break; + case KVM_EXIT_HYPERCALL: + ret = kvm_arm_handle_hypercall(cs, run->hypercall.nr); + break; default: qemu_log_mask(LOG_UNIMP, "%s: un-handled exit reason %d\n", __func__, run->exit_reason); @@ -981,7 +1023,12 @@ bool kvm_arch_stop_on_emulation_error(CPUState *cs)
int kvm_arch_process_async_events(CPUState *cs) { - return 0; + if (kvm_halt_in_kernel_allowed) { + return 0; + } + + /* If we're handling PSCI, don't call KVM_RUN for halted vCPUs */ + return cs->halted; }
void kvm_arch_update_guest_debug(CPUState *cs, struct kvm_guest_debug *dbg) diff --git a/target/arm/kvm64.c b/target/arm/kvm64.c index 8b9fd50ff6c..d7f1bfa6127 100644 --- a/target/arm/kvm64.c +++ b/target/arm/kvm64.c @@ -877,9 +877,8 @@ int kvm_arch_init_vcpu(CPUState *cs) }
/* - * When KVM is in use, PSCI is emulated in-kernel and not by qemu. - * Currently KVM has its own idea about MPIDR assignment, so we - * override our defaults with what we get from KVM. + * KVM may emulate PSCI in-kernel. Currently KVM has its own idea about + * MPIDR assignment, so we override our defaults with what we get from KVM. */ ret = kvm_get_one_reg(cs, ARM64_SYS_REG(ARM_CPU_ID_MPIDR), &mpidr); if (ret) {