The CPU_SUSPEND function is mandatory in PSCI v0.2+. As KVM handles timer interrupts and IPIs with the vGIC, it can implement wait-for- interrupt more easily than userspace. To implement CPU_SUSPEND, tell KVM to wait for interrupts before returning to the guest.
Signed-off-by: Jean-Philippe Brucker jean-philippe@linaro.org --- target/arm/cpu.h | 3 +++ target/arm/kvm.c | 17 +++++++++++++---- target/arm/psci.c | 14 +++++++++----- 3 files changed, 25 insertions(+), 9 deletions(-)
diff --git a/target/arm/cpu.h b/target/arm/cpu.h index 02ab8495665..fdc09e27b1a 100644 --- a/target/arm/cpu.h +++ b/target/arm/cpu.h @@ -882,6 +882,9 @@ struct ARMCPU { /* KVM steal time */ OnOffAuto kvm_steal_time;
+ /* Put the vCPU in WFI before returning to the guest */ + bool kvm_suspend; + /* Uniprocessor system with MP extensions */ bool mp_is_up;
diff --git a/target/arm/kvm.c b/target/arm/kvm.c index 56e460b388f..1a7e52b6bad 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -38,6 +38,7 @@ const KVMCapabilityInfo kvm_arch_required_capabilities[] = { };
static bool cap_has_mp_state; +static bool cap_has_mp_halted; static bool cap_has_inject_serror_esr; static bool cap_has_inject_ext_dabt;
@@ -256,6 +257,7 @@ int kvm_arch_init(MachineState *ms, KVMState *s) kvm_halt_in_kernel_allowed = true;
cap_has_mp_state = kvm_check_extension(s, KVM_CAP_MP_STATE); + cap_has_mp_halted = kvm_check_extension(s, KVM_CAP_ARM_MP_HALTED);
if (ms->smp.cpus > 256 && !kvm_check_extension(s, KVM_CAP_ARM_IRQ_LINE_LAYOUT_2)) { @@ -672,10 +674,16 @@ void kvm_arm_create_host_vcpu(ARMCPU *cpu) int kvm_arm_sync_mpstate_to_kvm(ARMCPU *cpu) { if (cap_has_mp_state) { - struct kvm_mp_state mp_state = { - .mp_state = (cpu->power_state == PSCI_OFF) ? - KVM_MP_STATE_STOPPED : KVM_MP_STATE_RUNNABLE - }; + struct kvm_mp_state mp_state = {}; + + if (cpu->power_state == PSCI_OFF) { + mp_state.mp_state = KVM_MP_STATE_STOPPED; + } else if (cap_has_mp_halted && cpu->kvm_suspend) { + mp_state.mp_state = KVM_MP_STATE_HALTED; + } else { + mp_state.mp_state = KVM_MP_STATE_RUNNABLE; + } + int ret = kvm_vcpu_ioctl(CPU(cpu), KVM_SET_MP_STATE, &mp_state); if (ret) { fprintf(stderr, "%s: failed to set MP_STATE %d/%s\n", @@ -702,6 +710,7 @@ int kvm_arm_sync_mpstate_to_qemu(ARMCPU *cpu) } cpu->power_state = (mp_state.mp_state == KVM_MP_STATE_STOPPED) ? PSCI_OFF : PSCI_ON; + cpu->kvm_suspend = (mp_state.mp_state == KVM_MP_STATE_HALTED); }
return 0; diff --git a/target/arm/psci.c b/target/arm/psci.c index 6709e280133..3a980c7f2bf 100644 --- a/target/arm/psci.c +++ b/target/arm/psci.c @@ -21,6 +21,7 @@ #include "exec/helper-proto.h" #include "kvm-consts.h" #include "qemu/main-loop.h" +#include "sysemu/kvm.h" #include "sysemu/runstate.h" #include "internals.h" #include "arm-powerctl.h" @@ -184,13 +185,16 @@ void arm_handle_psci_call(ARMCPU *cpu) ret = QEMU_PSCI_RET_INVALID_PARAMS; break; } - /* Powerdown is not supported, we always go into WFI */ - if (is_a64(env)) { - env->xregs[0] = 0; + /* + * Powerdown is not supported, we always go into WFI. + * Under KVM, let the kernel suspend the vCPU. + */ + if (kvm_enabled()) { + cpu->kvm_suspend = true; } else { - env->regs[0] = 0; + helper_wfi(env, 4); } - helper_wfi(env, 4); + ret = QEMU_ARM_POWERCTL_RET_SUCCESS; break; case QEMU_PSCI_0_1_FN_MIGRATE: case QEMU_PSCI_0_2_FN_MIGRATE: