@samitouri / QOSamiQemu / commits / a86024eb2d

target/arm/hvf: Fix WFI halting to stop idle vCPU spinning

Commit b5f8f77271 ("accel/hvf: Implement WFI without using pselect()") changed hvf_wfi() from blocking the vCPU thread with pselect() to returning EXCP_HLT, intending QEMU's main event loop to handle the idle wait. However, cpu->halted was never set, so cpu_thread_is_idle() always returns false and the vCPU thread spins at 100% CPU per core while the guest is idle. Fix this by: 1. Setting cpu->halted = 1 in hvf_wfi() so the vCPU thread sleeps on halt_cond in qemu_process_cpu_events(). 2. Arming a per-vCPU QEMU_CLOCK_VIRTUAL timer to fire when the guest's virtual timer (CNTV_CVAL_EL0) would expire. This is necessary because HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which is not called while the CPU is halted. The timer callback mirrors the VTIMER_ACTIVATED handler: it raises the vtimer IRQ through the GIC and marks vtimer_masked, causing the interrupt delivery chain to wake the vCPU via qemu_cpu_kick(). 3. Clearing cpu->halted in hvf_arch_vcpu_exec() when cpu_has_work() indicates a pending interrupt, and cancelling the WFI timer. 4. Re-arming the WFI timer from hvf_vm_state_change() on the resume transition for any halted vCPU, since the QEMUTimer is per-instance state and is not migrated. After cpu_synchronize_all_states() the migrated vtimer state is mirrored in env, so we can read CNTV_CTL and CNTV_CVAL from there. If the vtimer has already expired by the time the destination resumes, hvf_wfi_timer_cb() is invoked directly so the halted vCPU is woken up. All wfi_timer handling (allocation, arming, deletion, and the resume re-arm) is gated on !hvf_irqchip_in_kernel(): with the Apple in-kernel vGIC, HVF owns the vtimer and delivers wake-ups itself. Note for stable backports: this commit won't apply to 11.0 as it has changes to handle the hvf in-kernel irqchip support that landed after the 11.0 release. The v3 version of this commit: https://patchew.org/QEMU/20260427195516.46256-1-scottjgo@gmail.com/ should be suitable for 11.0 backporting (it is essentially identical except that it doesn't make the changes conditional on !hvf_irqchip_in_kernel()). Cc: qemu-stable@nongnu.org Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()") Signed-off-by: Scott J. Goldman <scottjgo@gmail.com> Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr> [PMM: added note about stable backports to commit message] Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

Scott J. Goldman committed May 12, 2026 at 22:21 UTC a86024eb2df56f7664cef16ec8e9a50a1dc3dd2f
2 files changed +140 -1
include/system/hvf_int.h
+1
@@ -48,6 +48,7 @@ struct AccelCPUState {
48 hv_vcpu_exit_t *exit;
49 bool vtimer_masked;
50 bool guest_debug_enabled;
51 + struct QEMUTimer *wfi_timer;
52 #endif
53 };
54
target/arm/hvf/hvf.c
+139 -1
@@ -29,6 +29,7 @@
29 #include "hw/core/irq.h"
30 #include "hw/arm/virt.h"
31 #include "qemu/main-loop.h"
32 +#include "qemu/timer.h"
33 #include "system/cpus.h"
34 #include "arm-powerctl.h"
35 #include "target/arm/cpu.h"
@@ -308,6 +309,8 @@ void hvf_arm_init_debug(void)
309 #define TMR_CTL_IMASK (1 << 1)
310 #define TMR_CTL_ISTATUS (1 << 2)
311
312 +static void hvf_wfi_timer_cb(void *opaque);
313 +
314 static uint32_t chosen_ipa_bit_size;
315
316 typedef struct HVFVTimer {
@@ -1296,6 +1299,11 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
1299 {
1300 hv_return_t ret;
1301
1302 + if (!hvf_irqchip_in_kernel()) {
1303 + timer_free(cpu->accel->wfi_timer);
1304 + cpu->accel->wfi_timer = NULL;
1305 + }
1306 +
1307 ret = hv_vcpu_destroy(cpu->accel->fd);
1308 assert_hvf_ok(ret);
1309 }
@@ -1487,6 +1495,11 @@ int hvf_arch_init_vcpu(CPUState *cpu)
1495 arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
1496 assert_hvf_ok(ret);
1497
1498 + if (!hvf_irqchip_in_kernel()) {
1499 + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
1500 + hvf_wfi_timer_cb, cpu);
1501 + }
1502 +
1503 aarch64_add_sme_properties(OBJECT(cpu));
1504 return 0;
1505 }
@@ -2194,6 +2207,62 @@ static uint64_t hvf_vtimer_val_raw(void)
2207 return mach_absolute_time() - hvf_state->vtimer_offset;
2208 }
2209
2210 +static void hvf_wfi_timer_cb(void *opaque)
2211 +{
2212 + CPUState *cpu = opaque;
2213 + ARMCPU *arm_cpu = ARM_CPU(cpu);
2214 +
2215 + /*
2216 + * vtimer expired while the CPU was halted for WFI.
2217 + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
2218 + * interrupt and mark as masked so hvf_sync_vtimer() will
2219 + * check and unmask when the guest handles it.
2220 + *
2221 + * The interrupt delivery chain (GIC -> cpu_interrupt ->
2222 + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
2223 + */
2224 + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
2225 + cpu->accel->vtimer_masked = true;
2226 +}
2227 +
2228 +/*
2229 + * Arm a host-side QEMU_CLOCK_VIRTUAL timer to fire when the guest's
2230 + * vtimer (CNTV_CVAL_EL0) is scheduled to expire. HVF only delivers
2231 + * HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which we won't
2232 + * call while the vCPU is halted, so we need this to wake the vCPU.
2233 + *
2234 + * QEMU_CLOCK_VIRTUAL pauses while the VM is stopped, which keeps the
2235 + * timer in lockstep with the guest's view of vtime across pause/resume.
2236 + *
2237 + * Caller must supply the current CNTV_CTL_EL0 and CNTV_CVAL_EL0 values,
2238 + * since the appropriate source (HVF vs. env) depends on context.
2239 + *
2240 + * Returns 0 if the timer was armed (or if the vtimer is disabled/masked
2241 + * and the vCPU should still halt waiting on another event), or -1 if
2242 + * the vtimer has already expired.
2243 + */
2244 +static int hvf_arm_wfi_timer(CPUState *cpu, uint64_t ctl, uint64_t cval)
2245 +{
2246 + ARMCPU *arm_cpu = ARM_CPU(cpu);
2247 + uint64_t now;
2248 + int64_t delta_ns;
2249 +
2250 + if (!(ctl & TMR_CTL_ENABLE) || (ctl & TMR_CTL_IMASK)) {
2251 + return 0;
2252 + }
2253 +
2254 + now = hvf_vtimer_val_raw();
2255 + if (cval <= now) {
2256 + return -1;
2257 + }
2258 +
2259 + delta_ns = muldiv64(cval - now, NANOSECONDS_PER_SECOND,
2260 + arm_cpu->gt_cntfrq_hz);
2261 + timer_mod(cpu->accel->wfi_timer,
2262 + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns);
2263 + return 0;
2264 +}
2265 +
2266 static int hvf_wfi(CPUState *cpu)
2267 {
2268 if (cpu_has_work(cpu)) {
@@ -2204,6 +2273,29 @@ static int hvf_wfi(CPUState *cpu)
2273 return 0;
2274 }
2275
2276 + if (!hvf_irqchip_in_kernel()) {
2277 + uint64_t ctl, cval;
2278 + hv_return_t r;
2279 +
2280 + /*
2281 + * Read the vtimer state directly from HVF. We're on the vCPU
2282 + * thread, just exited from hv_vcpu_run(), so HVF holds the
2283 + * authoritative values and env may be stale.
2284 + */
2285 + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0,
2286 + &ctl);
2287 + assert_hvf_ok(r);
2288 + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0,
2289 + &cval);
2290 + assert_hvf_ok(r);
2291 +
2292 + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
2293 + /* vtimer already expired, don't halt */
2294 + return 0;
2295 + }
2296 + }
2297 +
2298 + cpu->halted = 1;
2299 return EXCP_HLT;
2300 }
2301
@@ -2502,7 +2594,13 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
2594 hv_return_t r;
2595
2596 if (cpu->halted) {
2505 - return EXCP_HLT;
2597 + if (!cpu_has_work(cpu)) {
2598 + return EXCP_HLT;
2599 + }
2600 + cpu->halted = 0;
2601 + if (!hvf_irqchip_in_kernel()) {
2602 + timer_del(cpu->accel->wfi_timer);
2603 + }
2604 }
2605
2606 flush_cpu_state(cpu);
@@ -2551,6 +2649,46 @@ static void hvf_vm_state_change(void *opaque, bool running, RunState state)
2649 /* Update vtimer offset on all CPUs */
2650 hvf_state->vtimer_offset = mach_absolute_time() - s->vtimer_val;
2651 cpu_synchronize_all_states();
2652 +
2653 + /*
2654 + * After migration restore (or any resume), the wfi_timer is not
2655 + * scheduled on this QEMU instance, so re-arm it for any halted
2656 + * vCPU with a pending vtimer. For a non-migration resume the
2657 + * QEMU_CLOCK_VIRTUAL timer was already scheduled; recomputing the
2658 + * deadline produces the same value and is a harmless no-op.
2659 + *
2660 + * cpu_synchronize_all_states() above ensures env mirrors the
2661 + * authoritative vtimer state (whether that came from HVF or from
2662 + * the migration stream), so we can safely read it here from the
2663 + * iothread.
2664 + *
2665 + * Only applies when we own the wfi_timer; with an in-kernel vGIC
2666 + * the timer is never allocated and HVF handles vtimer wake-ups.
2667 + */
2668 + if (!hvf_irqchip_in_kernel()) {
2669 + CPUState *cpu;
2670 +
2671 + CPU_FOREACH(cpu) {
2672 + ARMCPU *arm_cpu;
2673 + uint64_t ctl, cval;
2674 +
2675 + if (!cpu->accel || !cpu->halted) {
2676 + continue;
2677 + }
2678 +
2679 + arm_cpu = ARM_CPU(cpu);
2680 + ctl = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].ctl;
2681 + cval = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].cval;
2682 +
2683 + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
2684 + /*
2685 + * vtimer already expired while we were paused; raise
2686 + * the IRQ now so the halted vCPU wakes up.
2687 + */
2688 + hvf_wfi_timer_cb(cpu);
2689 + }
2690 + }
2691 + }
2692 } else {
2693 /* Remember vtimer value on every pause */
2694 s->vtimer_val = hvf_vtimer_val_raw();