From: Author Salil Mehta <[email protected]> To support vCPU hotplug-like feature, we must trap any `HVC`/`SMC` `PSCI_CPU_{ON,OFF}` hypercalls from the host KVM to QEMU for policy checks. This ensures the following when a vCPU is brought online:
1. The vCPU is actually plugged in (i.e., present). 2. The vCPU is not administratively disabled. (Policy Checks) Implement the registration and handling of `HVC`/`SMC` hypercall exits within the VMM, ensuring that proper policy checks and control flow are enforced during the vCPU onlining and offlining processes. Co-developed-by: Jean-Philippe Brucker <[email protected]> Signed-off-by: Jean-Philippe Brucker <[email protected]> Signed-off-by: Salil Mehta <[email protected]> --- target/arm/arm-powerctl.c | 27 ++++++++--- target/arm/helper.c | 2 +- target/arm/internals.h | 2 +- target/arm/kvm.c | 93 +++++++++++++++++++++++++++++++++++++ target/arm/kvm_arm.h | 14 ++++++ target/arm/meson.build | 1 + target/arm/{tcg => }/psci.c | 9 ++++ target/arm/tcg/meson.build | 4 -- 8 files changed, 139 insertions(+), 13 deletions(-) rename target/arm/{tcg => }/psci.c (96%) diff --git a/target/arm/arm-powerctl.c b/target/arm/arm-powerctl.c index 20c70c7d6b..ab4422b261 100644 --- a/target/arm/arm-powerctl.c +++ b/target/arm/arm-powerctl.c @@ -17,6 +17,7 @@ #include "qemu/main-loop.h" #include "system/tcg.h" #include "target/arm/multiprocessing.h" +#include "hw/boards.h" #ifndef DEBUG_ARM_POWERCTL #define DEBUG_ARM_POWERCTL 0 @@ -31,14 +32,17 @@ CPUState *arm_get_cpu_by_id(uint64_t id) { + MachineState *ms = MACHINE(qdev_get_machine()); CPUState *cpu; DPRINTF("cpu %" PRId64 "\n", id); - CPU_FOREACH(cpu) { - ARMCPU *armcpu = ARM_CPU(cpu); - - if (arm_cpu_mp_affinity(armcpu) == id) { + /* + * with vCPU standy/hotplug support, we must now check for all + * possible vCPUs + */ + CPU_FOREACH_POSSIBLE(cpu, ms->possible_cpus) { + if (cpu && (arm_cpu_mp_affinity(ARM_CPU(cpu)) == id)) { return cpu; } } @@ -119,9 +123,18 @@ int arm_set_cpu_on(uint64_t cpuid, uint64_t entry, uint64_t context_id, /* Retrieve the cpu we are powering up */ target_cpu_state = arm_get_cpu_by_id(cpuid); - if (!target_cpu_state) { - /* The cpu was not found */ - return QEMU_ARM_POWERCTL_INVALID_PARAM; + + /* Policy check: verify 'administrative' power state of target CPU */ + if (!target_cpu_state || !qdev_check_enabled(DEVICE(target_cpu_state))) { + /* + * The cpu is not plugged in or disabled. We should return appropriate + * value as introduced in DEN0022E PSCI 1.2 issue E + */ + qemu_log_mask(LOG_GUEST_ERROR, + "[ARM]%s: Denying attempt to online ACPI disabled" + "(_STA.Ena=0)CPU%" PRId64", needs admin action first!\n", + __func__, cpuid); + return QEMU_ARM_POWERCTL_IS_OFF; } target_cpu = ARM_CPU(target_cpu_state); diff --git a/target/arm/helper.c b/target/arm/helper.c index 0c1299ff84..814fe719da 100644 --- a/target/arm/helper.c +++ b/target/arm/helper.c @@ -9110,7 +9110,7 @@ void arm_cpu_do_interrupt(CPUState *cs) env->exception.syndrome); } - if (tcg_enabled() && arm_is_psci_call(cpu, cs->exception_index)) { + if (arm_is_psci_call(cpu, cs->exception_index)) { arm_handle_psci_call(cpu); qemu_log_mask(CPU_LOG_INT, "...handled as PSCI call\n"); return; diff --git a/target/arm/internals.h b/target/arm/internals.h index 1b3d0244fd..ffd82a7ace 100644 --- a/target/arm/internals.h +++ b/target/arm/internals.h @@ -645,7 +645,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) 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 1962eb29b2..98eb6db9ed 100644 --- a/target/arm/kvm.c +++ b/target/arm/kvm.c @@ -529,9 +529,51 @@ int kvm_arch_get_default_type(MachineState *ms) return fixed_ipa ? 0 : size; } +static bool kvm_arm_set_vm_attr(struct kvm_device_attr *attr, const char *name) +{ + int err; + + err = kvm_vm_ioctl(kvm_state, KVM_HAS_DEVICE_ATTR, attr); + if (err != 0) { + error_report("%s: KVM_HAS_DEVICE_ATTR: %s", name, strerror(-err)); + return false; + } + + err = kvm_vm_ioctl(kvm_state, KVM_SET_DEVICE_ATTR, attr); + if (err != 0) { + error_report("%s: KVM_SET_DEVICE_ATTR: %s", name, strerror(-err)); + return false; + } + + return true; +} + +int kvm_arm_set_smccc_filter(uint64_t func, uint8_t faction) +{ + struct kvm_smccc_filter filter = { + .base = func, + .nr_functions = 1, + .action = faction, + }; + struct kvm_device_attr attr = { + .group = KVM_ARM_VM_SMCCC_CTRL, + .attr = KVM_ARM_VM_SMCCC_FILTER, + .flags = 0, + .addr = (uintptr_t)&filter, + }; + + if (!kvm_arm_set_vm_attr(&attr, "SMCCC Filter")) { + error_report("failed to set SMCCC filter in KVM Host"); + return -1; + } + + return 0; +} + int kvm_arch_init(MachineState *ms, KVMState *s) { int ret = 0; + /* For ARM interrupt delivery is always asynchronous, * whether we are using an in-kernel VGIC or not. */ @@ -594,6 +636,22 @@ int kvm_arch_init(MachineState *ms, KVMState *s) hw_breakpoints = g_array_sized_new(true, true, sizeof(HWBreakpoint), max_hw_bps); + /* + * To be able to handle PSCI CPU ON calls in QEMU, we need to install SMCCC + * filter in the Host KVM. This is required to support features like + * virtual CPU Hotplug on ARM platforms. + */ + if (kvm_arm_set_smccc_filter(PSCI_0_2_FN64_CPU_ON, + KVM_SMCCC_FILTER_FWD_TO_USER)) { + error_report("CPU On PSCI-to-user-space fwd filter install failed"); + abort(); + } + if (kvm_arm_set_smccc_filter(PSCI_0_2_FN_CPU_OFF, + KVM_SMCCC_FILTER_FWD_TO_USER)) { + error_report("CPU Off PSCI-to-user-space fwd filter install failed"); + abort(); + } + return ret; } @@ -1440,6 +1498,38 @@ static bool kvm_arm_handle_debug(ARMCPU *cpu, return false; } +static int kvm_arm_handle_hypercall(CPUState *cs, struct kvm_run *run) +{ + ARMCPU *cpu = ARM_CPU(cs); + CPUARMState *env = &cpu->env; + + kvm_cpu_synchronize_state(cs); + + /* + * hard coding immediate to 0 as we dont expect non-zero value as of now + * This might change in future versions. Hence, KVM_GET_ONE_REG could be + * used in such cases but it must be enhanced then only synchronize will + * also fetch ESR_EL2 value. + */ + if (run->hypercall.flags == KVM_HYPERCALL_EXIT_SMC) { + cs->exception_index = EXCP_SMC; + env->exception.syndrome = syn_aa64_smc(0); + } else { + cs->exception_index = EXCP_HVC; + env->exception.syndrome = syn_aa64_hvc(0); + } + env->exception.target_el = 1; + bql_lock(); + arm_cpu_do_interrupt(cs); + bql_unlock(); + + /* + * 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) { ARMCPU *cpu = ARM_CPU(cs); @@ -1456,6 +1546,9 @@ int kvm_arch_handle_exit(CPUState *cs, struct kvm_run *run) ret = kvm_arm_handle_dabt_nisv(cpu, run->arm_nisv.esr_iss, run->arm_nisv.fault_ipa); break; + case KVM_EXIT_HYPERCALL: + ret = kvm_arm_handle_hypercall(cs, run); + break; default: qemu_log_mask(LOG_UNIMP, "%s: un-handled exit reason %d\n", __func__, run->exit_reason); diff --git a/target/arm/kvm_arm.h b/target/arm/kvm_arm.h index ec9dc95ee8..bb2dfde3af 100644 --- a/target/arm/kvm_arm.h +++ b/target/arm/kvm_arm.h @@ -216,6 +216,15 @@ bool kvm_arm_mte_supported(void); * Returns true if KVM can enable EL2 and false otherwise. */ bool kvm_arm_el2_supported(void); + +/** + * kvm_arm_set_smccc_filter + * @func: funcion + * @faction: SMCCC filter action(handle, deny, fwd-to-user) to be deployed + * + * Sets the ARMs SMC-CC filter in KVM Host for selective hypercall exits + */ +int kvm_arm_set_smccc_filter(uint64_t func, uint8_t faction); #else static inline bool kvm_arm_aarch32_supported(void) @@ -242,6 +251,11 @@ static inline bool kvm_arm_el2_supported(void) { return false; } + +static inline int kvm_arm_set_smccc_filter(uint64_t func, uint8_t faction) +{ + g_assert_not_reached(); +} #endif /** diff --git a/target/arm/meson.build b/target/arm/meson.build index 07d9271aa4..ae4e75c4a9 100644 --- a/target/arm/meson.build +++ b/target/arm/meson.build @@ -15,6 +15,7 @@ arm_system_ss.add(files( )) arm_system_ss.add(when: 'CONFIG_KVM', if_true: files('hyp_gdbstub.c', 'kvm.c')) arm_system_ss.add(when: 'CONFIG_HVF', if_true: files('hyp_gdbstub.c')) +arm_system_ss.add(files('psci.c')) arm_user_ss = ss.source_set() arm_user_ss.add(files('cpu.c')) diff --git a/target/arm/tcg/psci.c b/target/arm/psci.c similarity index 96% rename from target/arm/tcg/psci.c rename to target/arm/psci.c index cabed43e8a..fbd2bd2d6f 100644 --- a/target/arm/tcg/psci.c +++ b/target/arm/psci.c @@ -21,10 +21,13 @@ #include "exec/helper-proto.h" #include "kvm-consts.h" #include "qemu/main-loop.h" +#include "qemu/error-report.h" #include "system/runstate.h" +#include "system/tcg.h" #include "internals.h" #include "arm-powerctl.h" #include "target/arm/multiprocessing.h" +#include "exec/target_long.h" bool arm_is_psci_call(ARMCPU *cpu, int excp_type) { @@ -158,6 +161,11 @@ void arm_handle_psci_call(ARMCPU *cpu) case QEMU_PSCI_0_1_FN_CPU_SUSPEND: case QEMU_PSCI_0_2_FN_CPU_SUSPEND: case QEMU_PSCI_0_2_FN64_CPU_SUSPEND: + if (!tcg_enabled()) { + warn_report("CPU suspend not supported in non-tcg mode"); + break; + } +#ifdef CONFIG_TCG /* Affinity levels are not supported in QEMU */ if (param[1] & 0xfffe0000) { ret = QEMU_PSCI_RET_INVALID_PARAMS; @@ -170,6 +178,7 @@ void arm_handle_psci_call(ARMCPU *cpu) env->regs[0] = 0; } helper_wfi(env, 4); +#endif break; case QEMU_PSCI_1_0_FN_PSCI_FEATURES: switch (param[1]) { diff --git a/target/arm/tcg/meson.build b/target/arm/tcg/meson.build index 895facdc30..f4d8db0f79 100644 --- a/target/arm/tcg/meson.build +++ b/target/arm/tcg/meson.build @@ -49,10 +49,6 @@ arm_ss.add(when: 'TARGET_AARCH64', if_true: files( 'sve_helper.c', )) -arm_system_ss.add(files( - 'psci.c', -)) - arm_system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('cpu-v7m.c')) arm_user_ss.add(when: 'TARGET_AARCH64', if_false: files('cpu-v7m.c')) -- 2.34.1
