* [PATCH] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
@ 2026-04-10 4:47 Scott J. Goldman
2026-04-10 5:06 ` Mohamed Mediouni
2026-04-10 5:50 ` [PATCH v2] " Scott J. Goldman
0 siblings, 2 replies; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-10 4:47 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, rbolshakov, phil, agraf, peter.maydell,
Scott J. Goldman
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 host-side QEMU_CLOCK_HOST 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.
Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/959
Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
---
include/system/hvf_int.h | 1 +
target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
index 2621164cb2..58fb865eba 100644
--- a/include/system/hvf_int.h
+++ b/include/system/hvf_int.h
@@ -48,6 +48,7 @@ struct AccelCPUState {
hv_vcpu_exit_t *exit;
bool vtimer_masked;
bool guest_debug_enabled;
+ struct QEMUTimer *wfi_timer;
#endif
};
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 5fc8f6bbbd..0e737d3583 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -28,6 +28,7 @@
#include "hw/core/boards.h"
#include "hw/core/irq.h"
#include "qemu/main-loop.h"
+#include "qemu/timer.h"
#include "system/cpus.h"
#include "arm-powerctl.h"
#include "target/arm/cpu.h"
@@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
#define TMR_CTL_IMASK (1 << 1)
#define TMR_CTL_ISTATUS (1 << 2)
+static void hvf_wfi_timer_cb(void *opaque);
+
static uint32_t chosen_ipa_bit_size;
typedef struct HVFVTimer {
@@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
{
hv_return_t ret;
+ timer_free(cpu->accel->wfi_timer);
+ cpu->accel->wfi_timer = NULL;
+
ret = hv_vcpu_destroy(cpu->accel->fd);
assert_hvf_ok(ret);
}
@@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
assert_hvf_ok(ret);
+ cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_HOST,
+ hvf_wfi_timer_cb, cpu);
+
aarch64_add_sme_properties(OBJECT(cpu));
return 0;
}
@@ -2027,8 +2036,29 @@ static uint64_t hvf_vtimer_val_raw(void)
return mach_absolute_time() - hvf_state->vtimer_offset;
}
+static void hvf_wfi_timer_cb(void *opaque)
+{
+ CPUState *cpu = opaque;
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+
+ /*
+ * vtimer expired while the CPU was halted for WFI.
+ * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
+ * interrupt and mark as masked so hvf_sync_vtimer() will
+ * check and unmask when the guest handles it.
+ *
+ * The interrupt delivery chain (GIC -> cpu_interrupt ->
+ * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
+ */
+ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
+ cpu->accel->vtimer_masked = true;
+}
+
static int hvf_wfi(CPUState *cpu)
{
+ uint64_t ctl, cval;
+ hv_return_t r;
+
if (cpu_has_work(cpu)) {
/*
* Don't bother to go into our "low power state" if
@@ -2037,6 +2067,35 @@ static int hvf_wfi(CPUState *cpu)
return 0;
}
+ /*
+ * Set up a host-side timer to wake us when the vtimer expires.
+ * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
+ * hv_vcpu_run(), which we won't call while halted.
+ */
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
+ assert_hvf_ok(r);
+
+ if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd,
+ HV_SYS_REG_CNTV_CVAL_EL0, &cval);
+ assert_hvf_ok(r);
+
+ uint64_t now = hvf_vtimer_val_raw();
+ if (cval <= now) {
+ /* Timer already expired, don't halt */
+ return 0;
+ }
+
+ uint64_t delta_ticks = cval - now;
+ mach_timebase_info_data_t timebase;
+ mach_timebase_info(&timebase);
+ int64_t delta_ns = delta_ticks * timebase.numer / timebase.denom;
+ int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
+
+ timer_mod(cpu->accel->wfi_timer, deadline);
+ }
+
+ cpu->halted = 1;
return EXCP_HLT;
}
@@ -2332,7 +2391,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
hv_return_t r;
if (cpu->halted) {
- return EXCP_HLT;
+ if (!cpu_has_work(cpu)) {
+ return EXCP_HLT;
+ }
+ cpu->halted = 0;
+ timer_del(cpu->accel->wfi_timer);
}
flush_cpu_state(cpu);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 4:47 [PATCH] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning Scott J. Goldman
@ 2026-04-10 5:06 ` Mohamed Mediouni
2026-04-10 5:28 ` Scott J. Goldman
2026-04-10 5:50 ` [PATCH v2] " Scott J. Goldman
1 sibling, 1 reply; 22+ messages in thread
From: Mohamed Mediouni @ 2026-04-10 5:06 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell
> On 10. Apr 2026, at 06:47, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> 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 host-side QEMU_CLOCK_HOST 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.
>
> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/959
> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
Hi,
Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
> ---
> include/system/hvf_int.h | 1 +
> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 65 insertions(+), 1 deletion(-)
>
> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
> index 2621164cb2..58fb865eba 100644
> --- a/include/system/hvf_int.h
> +++ b/include/system/hvf_int.h
> @@ -48,6 +48,7 @@ struct AccelCPUState {
> hv_vcpu_exit_t *exit;
> bool vtimer_masked;
> bool guest_debug_enabled;
> + struct QEMUTimer *wfi_timer;
> #endif
> };
>
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index 5fc8f6bbbd..0e737d3583 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -28,6 +28,7 @@
> #include "hw/core/boards.h"
> #include "hw/core/irq.h"
> #include "qemu/main-loop.h"
> +#include "qemu/timer.h"
> #include "system/cpus.h"
> #include "arm-powerctl.h"
> #include "target/arm/cpu.h"
> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
> #define TMR_CTL_IMASK (1 << 1)
> #define TMR_CTL_ISTATUS (1 << 2)
>
> +static void hvf_wfi_timer_cb(void *opaque);
> +
> static uint32_t chosen_ipa_bit_size;
>
> typedef struct HVFVTimer {
> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
> {
> hv_return_t ret;
>
> + timer_free(cpu->accel->wfi_timer);
> + cpu->accel->wfi_timer = NULL;
> +
> ret = hv_vcpu_destroy(cpu->accel->fd);
> assert_hvf_ok(ret);
> }
> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
> assert_hvf_ok(ret);
>
> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_HOST,
> + hvf_wfi_timer_cb, cpu);
> +
> aarch64_add_sme_properties(OBJECT(cpu));
> return 0;
> }
> @@ -2027,8 +2036,29 @@ static uint64_t hvf_vtimer_val_raw(void)
> return mach_absolute_time() - hvf_state->vtimer_offset;
> }
>
> +static void hvf_wfi_timer_cb(void *opaque)
> +{
> + CPUState *cpu = opaque;
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> +
> + /*
> + * vtimer expired while the CPU was halted for WFI.
> + *
> + * interrupt and mark as masked so hvf_sync_vtimer() will
> + * check and unmask when the guest handles it.
> + *
> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
> + */
> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
> + cpu->accel->vtimer_masked = true;
> +}
> +
> static int hvf_wfi(CPUState *cpu)
> {
> + uint64_t ctl, cval;
> + hv_return_t r;
> +
> if (cpu_has_work(cpu)) {
> /*
> * Don't bother to go into our "low power state" if
> @@ -2037,6 +2067,35 @@ static int hvf_wfi(CPUState *cpu)
> return 0;
> }
>
> + /*
> + * Set up a host-side timer to wake us when the vtimer expires.
> + * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
> + * hv_vcpu_run(), which we won't call while halted.
> + */
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
> + assert_hvf_ok(r);
> +
> + if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd,
> + HV_SYS_REG_CNTV_CVAL_EL0, &cval);
> + assert_hvf_ok(r);
> +
> + uint64_t now = hvf_vtimer_val_raw();
> + if (cval <= now) {
> + /* Timer already expired, don't halt */
> + return 0;
> + }
> +
> + uint64_t delta_ticks = cval - now;
We have CNTFRQ_EL0 in cpu->gt_cntfrq_hz but this works too
as XNU today tries to take care that host and VM run with
the same CNTFRQ_EL0
> + mach_timebase_info_data_t timebase;
> + mach_timebase_info(&timebase);
> + int64_t delta_ns = delta_ticks * timebase.numer / timebase.denom;
> + int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
> +
> + timer_mod(cpu->accel->wfi_timer, deadline);
> + }
> +
> + cpu->halted = 1;
> return EXCP_HLT;
> }
>
> @@ -2332,7 +2391,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
> hv_return_t r;
>
> if (cpu->halted) {
> - return EXCP_HLT;
> + if (!cpu_has_work(cpu)) {
> + return EXCP_HLT;
> + }
> + cpu->halted = 0;
> + timer_del(cpu->accel->wfi_timer);
> }
>
> flush_cpu_state(cpu);
> --
> 2.50.1 (Apple Git-155)
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 5:06 ` Mohamed Mediouni
@ 2026-04-10 5:28 ` Scott J. Goldman
0 siblings, 0 replies; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-10 5:28 UTC (permalink / raw)
To: Mohamed Mediouni, Scott J. Goldman
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell
On Thu Apr 9, 2026 at 10:06 PM PDT, Mohamed Mediouni wrote:
>
>
>> On 10. Apr 2026, at 06:47, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>
>> 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 host-side QEMU_CLOCK_HOST 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.
>>
>> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
>> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/959
>> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
>
> Hi,
>
> Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
>> ---
>> include/system/hvf_int.h | 1 +
>> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
>> 2 files changed, 65 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
>> index 2621164cb2..58fb865eba 100644
>> --- a/include/system/hvf_int.h
>> +++ b/include/system/hvf_int.h
>> @@ -48,6 +48,7 @@ struct AccelCPUState {
>> hv_vcpu_exit_t *exit;
>> bool vtimer_masked;
>> bool guest_debug_enabled;
>> + struct QEMUTimer *wfi_timer;
>> #endif
>> };
>>
>> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
>> index 5fc8f6bbbd..0e737d3583 100644
>> --- a/target/arm/hvf/hvf.c
>> +++ b/target/arm/hvf/hvf.c
>> @@ -28,6 +28,7 @@
>> #include "hw/core/boards.h"
>> #include "hw/core/irq.h"
>> #include "qemu/main-loop.h"
>> +#include "qemu/timer.h"
>> #include "system/cpus.h"
>> #include "arm-powerctl.h"
>> #include "target/arm/cpu.h"
>> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
>> #define TMR_CTL_IMASK (1 << 1)
>> #define TMR_CTL_ISTATUS (1 << 2)
>>
>> +static void hvf_wfi_timer_cb(void *opaque);
>> +
>> static uint32_t chosen_ipa_bit_size;
>>
>> typedef struct HVFVTimer {
>> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
>> {
>> hv_return_t ret;
>>
>> + timer_free(cpu->accel->wfi_timer);
>> + cpu->accel->wfi_timer = NULL;
>> +
>> ret = hv_vcpu_destroy(cpu->accel->fd);
>> assert_hvf_ok(ret);
>> }
>> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
>> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
>> assert_hvf_ok(ret);
>>
>> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_HOST,
>> + hvf_wfi_timer_cb, cpu);
>> +
>> aarch64_add_sme_properties(OBJECT(cpu));
>> return 0;
>> }
>> @@ -2027,8 +2036,29 @@ static uint64_t hvf_vtimer_val_raw(void)
>> return mach_absolute_time() - hvf_state->vtimer_offset;
>> }
>>
>> +static void hvf_wfi_timer_cb(void *opaque)
>> +{
>> + CPUState *cpu = opaque;
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> +
>> + /*
>> + * vtimer expired while the CPU was halted for WFI.
>> + *
>> + * interrupt and mark as masked so hvf_sync_vtimer() will
>> + * check and unmask when the guest handles it.
>> + *
>> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
>> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
>> + */
>> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
>> + cpu->accel->vtimer_masked = true;
>> +}
>> +
>> static int hvf_wfi(CPUState *cpu)
>> {
>> + uint64_t ctl, cval;
>> + hv_return_t r;
>> +
>> if (cpu_has_work(cpu)) {
>> /*
>> * Don't bother to go into our "low power state" if
>> @@ -2037,6 +2067,35 @@ static int hvf_wfi(CPUState *cpu)
>> return 0;
>> }
>>
>> + /*
>> + * Set up a host-side timer to wake us when the vtimer expires.
>> + * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
>> + * hv_vcpu_run(), which we won't call while halted.
>> + */
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
>> + assert_hvf_ok(r);
>> +
>> + if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd,
>> + HV_SYS_REG_CNTV_CVAL_EL0, &cval);
>> + assert_hvf_ok(r);
>> +
>> + uint64_t now = hvf_vtimer_val_raw();
>> + if (cval <= now) {
>> + /* Timer already expired, don't halt */
>> + return 0;
>> + }
>> +
>> + uint64_t delta_ticks = cval - now;
>
> We have CNTFRQ_EL0 in cpu->gt_cntfrq_hz but this works too
> as XNU today tries to take care that host and VM run with
> the same CNTFRQ_EL0
>
Ah, I can post a follow-up.
>> + mach_timebase_info_data_t timebase;
>> + mach_timebase_info(&timebase);
>> + int64_t delta_ns = delta_ticks * timebase.numer / timebase.denom;
>> + int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
>> +
>> + timer_mod(cpu->accel->wfi_timer, deadline);
>> + }
>> +
>> + cpu->halted = 1;
>> return EXCP_HLT;
>> }
>>
>> @@ -2332,7 +2391,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
>> hv_return_t r;
>>
>> if (cpu->halted) {
>> - return EXCP_HLT;
>> + if (!cpu_has_work(cpu)) {
>> + return EXCP_HLT;
>> + }
>> + cpu->halted = 0;
>> + timer_del(cpu->accel->wfi_timer);
>> }
>>
>> flush_cpu_state(cpu);
>> --
>> 2.50.1 (Apple Git-155)
>>
>>
Thanks,
-sjg
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 4:47 [PATCH] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning Scott J. Goldman
2026-04-10 5:06 ` Mohamed Mediouni
@ 2026-04-10 5:50 ` Scott J. Goldman
2026-04-10 6:18 ` Mohamed Mediouni
` (2 more replies)
1 sibling, 3 replies; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-10 5:50 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, rbolshakov, phil, agraf, peter.maydell,
Scott J. Goldman
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 host-side QEMU_CLOCK_HOST 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.
Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
---
include/system/hvf_int.h | 1 +
target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
2 files changed, 65 insertions(+), 1 deletion(-)
diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
index 2621164cb2..58fb865eba 100644
--- a/include/system/hvf_int.h
+++ b/include/system/hvf_int.h
@@ -48,6 +48,7 @@ struct AccelCPUState {
hv_vcpu_exit_t *exit;
bool vtimer_masked;
bool guest_debug_enabled;
+ struct QEMUTimer *wfi_timer;
#endif
};
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 5fc8f6bbbd..f5d7221845 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -28,6 +28,7 @@
#include "hw/core/boards.h"
#include "hw/core/irq.h"
#include "qemu/main-loop.h"
+#include "qemu/timer.h"
#include "system/cpus.h"
#include "arm-powerctl.h"
#include "target/arm/cpu.h"
@@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
#define TMR_CTL_IMASK (1 << 1)
#define TMR_CTL_ISTATUS (1 << 2)
+static void hvf_wfi_timer_cb(void *opaque);
+
static uint32_t chosen_ipa_bit_size;
typedef struct HVFVTimer {
@@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
{
hv_return_t ret;
+ timer_free(cpu->accel->wfi_timer);
+ cpu->accel->wfi_timer = NULL;
+
ret = hv_vcpu_destroy(cpu->accel->fd);
assert_hvf_ok(ret);
}
@@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
assert_hvf_ok(ret);
+ cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_HOST,
+ hvf_wfi_timer_cb, cpu);
+
aarch64_add_sme_properties(OBJECT(cpu));
return 0;
}
@@ -2027,8 +2036,30 @@ static uint64_t hvf_vtimer_val_raw(void)
return mach_absolute_time() - hvf_state->vtimer_offset;
}
+static void hvf_wfi_timer_cb(void *opaque)
+{
+ CPUState *cpu = opaque;
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+
+ /*
+ * vtimer expired while the CPU was halted for WFI.
+ * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
+ * interrupt and mark as masked so hvf_sync_vtimer() will
+ * check and unmask when the guest handles it.
+ *
+ * The interrupt delivery chain (GIC -> cpu_interrupt ->
+ * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
+ */
+ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
+ cpu->accel->vtimer_masked = true;
+}
+
static int hvf_wfi(CPUState *cpu)
{
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+ uint64_t ctl, cval;
+ hv_return_t r;
+
if (cpu_has_work(cpu)) {
/*
* Don't bother to go into our "low power state" if
@@ -2037,6 +2068,34 @@ static int hvf_wfi(CPUState *cpu)
return 0;
}
+ /*
+ * Set up a host-side timer to wake us when the vtimer expires.
+ * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
+ * hv_vcpu_run(), which we won't call while halted.
+ */
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
+ assert_hvf_ok(r);
+
+ if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd,
+ HV_SYS_REG_CNTV_CVAL_EL0, &cval);
+ assert_hvf_ok(r);
+
+ uint64_t now = hvf_vtimer_val_raw();
+ if (cval <= now) {
+ /* Timer already expired, don't halt */
+ return 0;
+ }
+
+ uint64_t delta_ticks = cval - now;
+ int64_t delta_ns = delta_ticks * NANOSECONDS_PER_SECOND
+ / arm_cpu->gt_cntfrq_hz;
+ int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
+
+ timer_mod(cpu->accel->wfi_timer, deadline);
+ }
+
+ cpu->halted = 1;
return EXCP_HLT;
}
@@ -2332,7 +2391,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
hv_return_t r;
if (cpu->halted) {
- return EXCP_HLT;
+ if (!cpu_has_work(cpu)) {
+ return EXCP_HLT;
+ }
+ cpu->halted = 0;
+ timer_del(cpu->accel->wfi_timer);
}
flush_cpu_state(cpu);
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 5:50 ` [PATCH v2] " Scott J. Goldman
@ 2026-04-10 6:18 ` Mohamed Mediouni
2026-04-16 21:20 ` Scott J. Goldman
2026-04-27 11:15 ` Peter Maydell
2026-04-27 19:55 ` [PATCH v3] " Scott J. Goldman
2 siblings, 1 reply; 22+ messages in thread
From: Mohamed Mediouni @ 2026-04-10 6:18 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell
> On 10. Apr 2026, at 07:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> 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 host-side QEMU_CLOCK_HOST 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.
>
> 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>
> ---
> include/system/hvf_int.h | 1 +
> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 65 insertions(+), 1 deletion(-)
>
> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
> index 2621164cb2..58fb865eba 100644
> --- a/include/system/hvf_int.h
> +++ b/include/system/hvf_int.h
> @@ -48,6 +48,7 @@ struct AccelCPUState {
> hv_vcpu_exit_t *exit;
> bool vtimer_masked;
> bool guest_debug_enabled;
> + struct QEMUTimer *wfi_timer;
> #endif
> };
>
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index 5fc8f6bbbd..f5d7221845 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -28,6 +28,7 @@
> #include "hw/core/boards.h"
> #include "hw/core/irq.h"
> #include "qemu/main-loop.h"
> +#include "qemu/timer.h"
> #include "system/cpus.h"
> #include "arm-powerctl.h"
> #include "target/arm/cpu.h"
> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
> #define TMR_CTL_IMASK (1 << 1)
> #define TMR_CTL_ISTATUS (1 << 2)
>
> +static void hvf_wfi_timer_cb(void *opaque);
> +
> static uint32_t chosen_ipa_bit_size;
>
> typedef struct HVFVTimer {
> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
> {
> hv_return_t ret;
>
> + timer_free(cpu->accel->wfi_timer);
> + cpu->accel->wfi_timer = NULL;
> +
> ret = hv_vcpu_destroy(cpu->accel->fd);
> assert_hvf_ok(ret);
> }
> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
> assert_hvf_ok(ret);
>
> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_HOST,
> + hvf_wfi_timer_cb, cpu);
> +
> aarch64_add_sme_properties(OBJECT(cpu));
> return 0;
> }
> @@ -2027,8 +2036,30 @@ static uint64_t hvf_vtimer_val_raw(void)
> return mach_absolute_time() - hvf_state->vtimer_offset;
> }
>
> +static void hvf_wfi_timer_cb(void *opaque)
> +{
> + CPUState *cpu = opaque;
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> +
> + /*
> + * vtimer expired while the CPU was halted for WFI.
> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
> + * interrupt and mark as masked so hvf_sync_vtimer() will
> + * check and unmask when the guest handles it.
> + *
> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
> + */
> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
> + cpu->accel->vtimer_masked = true;
> +}
> +
> static int hvf_wfi(CPUState *cpu)
> {
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> + uint64_t ctl, cval;
> + hv_return_t r;
> +
> if (cpu_has_work(cpu)) {
> /*
> * Don't bother to go into our "low power state" if
> @@ -2037,6 +2068,34 @@ static int hvf_wfi(CPUState *cpu)
> return 0;
> }
>
> + /*
> + * Set up a host-side timer to wake us when the vtimer expires.
> + * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
> + * hv_vcpu_run(), which we won't call while halted.
> + */
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
> + assert_hvf_ok(r);
> +
> + if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd,
> + HV_SYS_REG_CNTV_CVAL_EL0, &cval);
> + assert_hvf_ok(r);
> +
> + uint64_t now = hvf_vtimer_val_raw();
> + if (cval <= now) {
> + /* Timer already expired, don't halt */
> + return 0;
> + }
> +
> + uint64_t delta_ticks = cval - now;
> + int64_t delta_ns = delta_ticks * NANOSECONDS_PER_SECOND
> + / arm_cpu->gt_cntfrq_hz;
> + int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
> +
> + timer_mod(cpu->accel->wfi_timer, deadline);
> + }
> +
> + cpu->halted = 1;
> return EXCP_HLT;
> }
>
> @@ -2332,7 +2391,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
> hv_return_t r;
>
> if (cpu->halted) {
> - return EXCP_HLT;
> + if (!cpu_has_work(cpu)) {
> + return EXCP_HLT;
> + }
> + cpu->halted = 0;
> + timer_del(cpu->accel->wfi_timer);
> }
>
> flush_cpu_state(cpu);
> --
> 2.50.1 (Apple Git-155)
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 6:18 ` Mohamed Mediouni
@ 2026-04-16 21:20 ` Scott J. Goldman
2026-04-17 9:57 ` Philippe Mathieu-Daudé
0 siblings, 1 reply; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-16 21:20 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell,
Mohamed Mediouni
Philippe-- apologies for the nag here but this is a fix for a unfortunate
regression. Any chance you could take a look? Thanks!
On Thu Apr 9, 2026 at 11:18 PM PDT, Mohamed Mediouni wrote:
>
>
>> On 10. Apr 2026, at 07:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>
>> 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 host-side QEMU_CLOCK_HOST 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.
>>
>> 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>
>> ---
>> include/system/hvf_int.h | 1 +
>> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
>> 2 files changed, 65 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
>> index 2621164cb2..58fb865eba 100644
>> --- a/include/system/hvf_int.h
>> +++ b/include/system/hvf_int.h
>> @@ -48,6 +48,7 @@ struct AccelCPUState {
>> hv_vcpu_exit_t *exit;
>> bool vtimer_masked;
>> bool guest_debug_enabled;
>> + struct QEMUTimer *wfi_timer;
>> #endif
>> };
>>
>> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
>> index 5fc8f6bbbd..f5d7221845 100644
>> --- a/target/arm/hvf/hvf.c
>> +++ b/target/arm/hvf/hvf.c
>> @@ -28,6 +28,7 @@
>> #include "hw/core/boards.h"
>> #include "hw/core/irq.h"
>> #include "qemu/main-loop.h"
>> +#include "qemu/timer.h"
>> #include "system/cpus.h"
>> #include "arm-powerctl.h"
>> #include "target/arm/cpu.h"
>> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
>> #define TMR_CTL_IMASK (1 << 1)
>> #define TMR_CTL_ISTATUS (1 << 2)
>>
>> +static void hvf_wfi_timer_cb(void *opaque);
>> +
>> static uint32_t chosen_ipa_bit_size;
>>
>> typedef struct HVFVTimer {
>> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
>> {
>> hv_return_t ret;
>>
>> + timer_free(cpu->accel->wfi_timer);
>> + cpu->accel->wfi_timer = NULL;
>> +
>> ret = hv_vcpu_destroy(cpu->accel->fd);
>> assert_hvf_ok(ret);
>> }
>> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
>> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
>> assert_hvf_ok(ret);
>>
>> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_HOST,
>> + hvf_wfi_timer_cb, cpu);
>> +
>> aarch64_add_sme_properties(OBJECT(cpu));
>> return 0;
>> }
>> @@ -2027,8 +2036,30 @@ static uint64_t hvf_vtimer_val_raw(void)
>> return mach_absolute_time() - hvf_state->vtimer_offset;
>> }
>>
>> +static void hvf_wfi_timer_cb(void *opaque)
>> +{
>> + CPUState *cpu = opaque;
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> +
>> + /*
>> + * vtimer expired while the CPU was halted for WFI.
>> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
>> + * interrupt and mark as masked so hvf_sync_vtimer() will
>> + * check and unmask when the guest handles it.
>> + *
>> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
>> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
>> + */
>> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
>> + cpu->accel->vtimer_masked = true;
>> +}
>> +
>> static int hvf_wfi(CPUState *cpu)
>> {
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> + uint64_t ctl, cval;
>> + hv_return_t r;
>> +
>> if (cpu_has_work(cpu)) {
>> /*
>> * Don't bother to go into our "low power state" if
>> @@ -2037,6 +2068,34 @@ static int hvf_wfi(CPUState *cpu)
>> return 0;
>> }
>>
>> + /*
>> + * Set up a host-side timer to wake us when the vtimer expires.
>> + * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
>> + * hv_vcpu_run(), which we won't call while halted.
>> + */
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
>> + assert_hvf_ok(r);
>> +
>> + if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd,
>> + HV_SYS_REG_CNTV_CVAL_EL0, &cval);
>> + assert_hvf_ok(r);
>> +
>> + uint64_t now = hvf_vtimer_val_raw();
>> + if (cval <= now) {
>> + /* Timer already expired, don't halt */
>> + return 0;
>> + }
>> +
>> + uint64_t delta_ticks = cval - now;
>> + int64_t delta_ns = delta_ticks * NANOSECONDS_PER_SECOND
>> + / arm_cpu->gt_cntfrq_hz;
>> + int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
>> +
>> + timer_mod(cpu->accel->wfi_timer, deadline);
>> + }
>> +
>> + cpu->halted = 1;
>> return EXCP_HLT;
>> }
>>
>> @@ -2332,7 +2391,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
>> hv_return_t r;
>>
>> if (cpu->halted) {
>> - return EXCP_HLT;
>> + if (!cpu_has_work(cpu)) {
>> + return EXCP_HLT;
>> + }
>> + cpu->halted = 0;
>> + timer_del(cpu->accel->wfi_timer);
>> }
>>
>> flush_cpu_state(cpu);
>> --
>> 2.50.1 (Apple Git-155)
>>
>>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-16 21:20 ` Scott J. Goldman
@ 2026-04-17 9:57 ` Philippe Mathieu-Daudé
2026-04-17 20:30 ` Scott J. Goldman
0 siblings, 1 reply; 22+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-17 9:57 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell,
Mohamed Mediouni
Hi Scott,
On 16/4/26 23:20, Scott J. Goldman wrote:
> Philippe-- apologies for the nag here but this is a fix for a unfortunate
> regression. Any chance you could take a look? Thanks!
>
> On Thu Apr 9, 2026 at 11:18 PM PDT, Mohamed Mediouni wrote:
>>
>>
>>> On 10. Apr 2026, at 07:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>>
>>> 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 host-side QEMU_CLOCK_HOST 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.
>>>
>>> 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>
>>> ---
>>> include/system/hvf_int.h | 1 +
>>> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
>>> 2 files changed, 65 insertions(+), 1 deletion(-)
I haven't looked at your patch yet. While looking at commit
b5f8f77271 I noticed the previous a14afa985e3 ("accel/hvf: Skip
WFI if CPU has work to do") is different in my local tree, where
I have:
-- >8 --
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 5fc8f6bbbd9..1a76c9cf402 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -2037,6 +2037,11 @@ static int hvf_wfi(CPUState *cpu)
return 0;
}
+ if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIQ)) {
+ /* Interrupt pending, no need to wait */
+ return EXCP_INTERRUPT;
+ }
+
return EXCP_HLT;
}
---
Does that help? Now I'm really confused.
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-17 9:57 ` Philippe Mathieu-Daudé
@ 2026-04-17 20:30 ` Scott J. Goldman
2026-04-21 23:24 ` Scott J. Goldman
0 siblings, 1 reply; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-17 20:30 UTC (permalink / raw)
To: Philippe Mathieu-Daudé, Scott J. Goldman
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell,
Mohamed Mediouni
On Fri Apr 17, 2026 at 2:57 AM PDT, Philippe Mathieu-Daudé wrote:
> Hi Scott,
>
> On 16/4/26 23:20, Scott J. Goldman wrote:
>> Philippe-- apologies for the nag here but this is a fix for a unfortunate
>> regression. Any chance you could take a look? Thanks!
>>
>> On Thu Apr 9, 2026 at 11:18 PM PDT, Mohamed Mediouni wrote:
>>>
>>>
>>>> On 10. Apr 2026, at 07:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>>>
>>>> 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 host-side QEMU_CLOCK_HOST 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.
>>>>
>>>> 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>
>>>> ---
>>>> include/system/hvf_int.h | 1 +
>>>> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
>>>> 2 files changed, 65 insertions(+), 1 deletion(-)
>
> I haven't looked at your patch yet. While looking at commit
> b5f8f77271 I noticed the previous a14afa985e3 ("accel/hvf: Skip
> WFI if CPU has work to do") is different in my local tree, where
> I have:
>
> -- >8 --
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index 5fc8f6bbbd9..1a76c9cf402 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -2037,6 +2037,11 @@ static int hvf_wfi(CPUState *cpu)
> return 0;
> }
>
> + if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIQ)) {
> + /* Interrupt pending, no need to wait */
> + return EXCP_INTERRUPT;
> + }
> +
> return EXCP_HLT;
> }
>
> ---
>
> Does that help? Now I'm really confused.
Hi Phillipe-- Appreciate your help here. The good news is the bug is
very easy to reproduce (start a linux VM with 1 cpu, watch the vCPU on
the host will always spin 100%). The bad news is I double checked my
bisect and you can see before b5f8f77271 there is no problem, and after
the commit is introduced, the problem appears. I also applied the
additional cpu_test_interrupt() patch you suggested and it did not
change the behavior.
thanks
-sjg
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-17 20:30 ` Scott J. Goldman
@ 2026-04-21 23:24 ` Scott J. Goldman
0 siblings, 0 replies; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-21 23:24 UTC (permalink / raw)
To: Scott J. Goldman, Philippe Mathieu-Daudé
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf, peter.maydell,
Mohamed Mediouni
On Fri Apr 17, 2026 at 1:30 PM PDT, Scott J. Goldman wrote:
> On Fri Apr 17, 2026 at 2:57 AM PDT, Philippe Mathieu-Daudé wrote:
>> Hi Scott,
>>
>> On 16/4/26 23:20, Scott J. Goldman wrote:
>>> Philippe-- apologies for the nag here but this is a fix for a unfortunate
>>> regression. Any chance you could take a look? Thanks!
>>>
>>> On Thu Apr 9, 2026 at 11:18 PM PDT, Mohamed Mediouni wrote:
>>>>
>>>>
>>>>> On 10. Apr 2026, at 07:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>>>>
>>>>> 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 host-side QEMU_CLOCK_HOST 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.
>>>>>
>>>>> 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>
>>>>> ---
>>>>> include/system/hvf_int.h | 1 +
>>>>> target/arm/hvf/hvf.c | 65 +++++++++++++++++++++++++++++++++++++++-
>>>>> 2 files changed, 65 insertions(+), 1 deletion(-)
>>
>> I haven't looked at your patch yet. While looking at commit
>> b5f8f77271 I noticed the previous a14afa985e3 ("accel/hvf: Skip
>> WFI if CPU has work to do") is different in my local tree, where
>> I have:
>>
>> -- >8 --
>> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
>> index 5fc8f6bbbd9..1a76c9cf402 100644
>> --- a/target/arm/hvf/hvf.c
>> +++ b/target/arm/hvf/hvf.c
>> @@ -2037,6 +2037,11 @@ static int hvf_wfi(CPUState *cpu)
>> return 0;
>> }
>>
>> + if (cpu_test_interrupt(cpu, CPU_INTERRUPT_HARD | CPU_INTERRUPT_FIQ)) {
>> + /* Interrupt pending, no need to wait */
>> + return EXCP_INTERRUPT;
>> + }
>> +
>> return EXCP_HLT;
>> }
>>
>> ---
>>
>> Does that help? Now I'm really confused.
>
>
> Hi Phillipe-- Appreciate your help here. The good news is the bug is
> very easy to reproduce (start a linux VM with 1 cpu, watch the vCPU on
> the host will always spin 100%). The bad news is I double checked my
> bisect and you can see before b5f8f77271 there is no problem, and after
> the commit is introduced, the problem appears. I also applied the
> additional cpu_test_interrupt() patch you suggested and it did not
> change the behavior.
>
> thanks
> -sjg
Hi Philippe- Apologies again for bugging you, but it looks like v11 is
about to be released and it would be a bummer if this regression went
out with the stable version. Have you had a chance to either look at my
patch or check if there was some other simpler fix?
Also, I'm not a regular qemu contributor, so if there is some process
here, that I'm missing, please let me know.
thanks
-sjg
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 5:50 ` [PATCH v2] " Scott J. Goldman
2026-04-10 6:18 ` Mohamed Mediouni
@ 2026-04-27 11:15 ` Peter Maydell
2026-04-27 18:42 ` Scott J. Goldman
2026-04-27 19:55 ` [PATCH v3] " Scott J. Goldman
2 siblings, 1 reply; 22+ messages in thread
From: Peter Maydell @ 2026-04-27 11:15 UTC (permalink / raw)
To: Scott J. Goldman; +Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf
On Fri, 10 Apr 2026 at 06:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> 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 host-side QEMU_CLOCK_HOST 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().
A host side timer here seems a bit odd -- it will still
expire even if the user halts the whole VM. We only use
QEMU_CLOCK_HOST timers for things that are part of QEMU proper,
like handling timeouts in the migration networking code.
> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
> index 2621164cb2..58fb865eba 100644
> --- a/include/system/hvf_int.h
> +++ b/include/system/hvf_int.h
> @@ -48,6 +48,7 @@ struct AccelCPUState {
> hv_vcpu_exit_t *exit;
> bool vtimer_masked;
> bool guest_debug_enabled;
> + struct QEMUTimer *wfi_timer;
> #endif
> };
QEMUTimer objects need to be migrated. I think as it is now,
if you do a migration while one vCPU is in WFI we won't
migrate the timer state, and so on the destination it won't
be woken up when it should.
> +static void hvf_wfi_timer_cb(void *opaque)
> +{
> + CPUState *cpu = opaque;
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> +
> + /*
> + * vtimer expired while the CPU was halted for WFI.
> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
> + * interrupt and mark as masked so hvf_sync_vtimer() will
> + * check and unmask when the guest handles it.
> + *
> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
> + */
> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
> + cpu->accel->vtimer_masked = true;
If the VM happens to be stopped by the user when the timer
fires, this will change the VM state, which I suspect is
not correct. Avoiding use of a QEMU_CLOCK_HOST timer would
fix this.
> +}
> +
> static int hvf_wfi(CPUState *cpu)
> {
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> + uint64_t ctl, cval;
> + hv_return_t r;
> +
> if (cpu_has_work(cpu)) {
> /*
> * Don't bother to go into our "low power state" if
> @@ -2037,6 +2068,34 @@ static int hvf_wfi(CPUState *cpu)
> return 0;
> }
>
> + /*
> + * Set up a host-side timer to wake us when the vtimer expires.
> + * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
> + * hv_vcpu_run(), which we won't call while halted.
> + */
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
> + assert_hvf_ok(r);
> +
> + if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd,
> + HV_SYS_REG_CNTV_CVAL_EL0, &cval);
> + assert_hvf_ok(r);
> +
> + uint64_t now = hvf_vtimer_val_raw();
> + if (cval <= now) {
> + /* Timer already expired, don't halt */
> + return 0;
> + }
> +
> + uint64_t delta_ticks = cval - now;
> + int64_t delta_ns = delta_ticks * NANOSECONDS_PER_SECOND
> + / arm_cpu->gt_cntfrq_hz;
As a general rule, tick-to-ns and vice-versa conversions should
use muldiv64() to avoid potential overflow in the intermediate value.
> + int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
> +
> + timer_mod(cpu->accel->wfi_timer, deadline);
> + }
> +
> + cpu->halted = 1;
> return EXCP_HLT;
> }
thanks
-- PMM
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v2] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-27 11:15 ` Peter Maydell
@ 2026-04-27 18:42 ` Scott J. Goldman
0 siblings, 0 replies; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-27 18:42 UTC (permalink / raw)
To: Peter Maydell, Scott J. Goldman
Cc: qemu-devel, qemu-arm, rbolshakov, phil, agraf
On Mon Apr 27, 2026 at 4:15 AM PDT, Peter Maydell wrote:
> On Fri, 10 Apr 2026 at 06:50, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>
>> 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 host-side QEMU_CLOCK_HOST 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().
>
> A host side timer here seems a bit odd -- it will still
> expire even if the user halts the whole VM. We only use
> QEMU_CLOCK_HOST timers for things that are part of QEMU proper,
> like handling timeouts in the migration networking code.
>
>> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
>> index 2621164cb2..58fb865eba 100644
>> --- a/include/system/hvf_int.h
>> +++ b/include/system/hvf_int.h
>> @@ -48,6 +48,7 @@ struct AccelCPUState {
>> hv_vcpu_exit_t *exit;
>> bool vtimer_masked;
>> bool guest_debug_enabled;
>> + struct QEMUTimer *wfi_timer;
>> #endif
>> };
>
> QEMUTimer objects need to be migrated. I think as it is now,
> if you do a migration while one vCPU is in WFI we won't
> migrate the timer state, and so on the destination it won't
> be woken up when it should.
Hi Peter--
Thanks for the review here. I am going to post a follow-up patch based
on your comments, but I did want to just flag that migration is broken
in hvf/arm right now (segfaults in the dirty page tracking code). Of
course, I can post fixes for that as well.
>
>> +static void hvf_wfi_timer_cb(void *opaque)
>> +{
>> + CPUState *cpu = opaque;
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> +
>> + /*
>> + * vtimer expired while the CPU was halted for WFI.
>> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
>> + * interrupt and mark as masked so hvf_sync_vtimer() will
>> + * check and unmask when the guest handles it.
>> + *
>> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
>> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
>> + */
>> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
>> + cpu->accel->vtimer_masked = true;
>
> If the VM happens to be stopped by the user when the timer
> fires, this will change the VM state, which I suspect is
> not correct. Avoiding use of a QEMU_CLOCK_HOST timer would
> fix this.
>
>> +}
>> +
>> static int hvf_wfi(CPUState *cpu)
>> {
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> + uint64_t ctl, cval;
>> + hv_return_t r;
>> +
>> if (cpu_has_work(cpu)) {
>> /*
>> * Don't bother to go into our "low power state" if
>> @@ -2037,6 +2068,34 @@ static int hvf_wfi(CPUState *cpu)
>> return 0;
>> }
>>
>> + /*
>> + * Set up a host-side timer to wake us when the vtimer expires.
>> + * HVF only delivers HV_EXIT_REASON_VTIMER_ACTIVATED during
>> + * hv_vcpu_run(), which we won't call while halted.
>> + */
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
>> + assert_hvf_ok(r);
>> +
>> + if ((ctl & TMR_CTL_ENABLE) && !(ctl & TMR_CTL_IMASK)) {
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd,
>> + HV_SYS_REG_CNTV_CVAL_EL0, &cval);
>> + assert_hvf_ok(r);
>> +
>> + uint64_t now = hvf_vtimer_val_raw();
>> + if (cval <= now) {
>> + /* Timer already expired, don't halt */
>> + return 0;
>> + }
>> +
>> + uint64_t delta_ticks = cval - now;
>> + int64_t delta_ns = delta_ticks * NANOSECONDS_PER_SECOND
>> + / arm_cpu->gt_cntfrq_hz;
>
> As a general rule, tick-to-ns and vice-versa conversions should
> use muldiv64() to avoid potential overflow in the intermediate value.
>
>> + int64_t deadline = qemu_clock_get_ns(QEMU_CLOCK_HOST) + delta_ns;
>> +
>> + timer_mod(cpu->accel->wfi_timer, deadline);
>> + }
>> +
>> + cpu->halted = 1;
>> return EXCP_HLT;
>> }
>
> thanks
> -- PMM
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-10 5:50 ` [PATCH v2] " Scott J. Goldman
2026-04-10 6:18 ` Mohamed Mediouni
2026-04-27 11:15 ` Peter Maydell
@ 2026-04-27 19:55 ` Scott J. Goldman
2026-05-12 16:46 ` Scott J. Goldman
` (2 more replies)
2 siblings, 3 replies; 22+ messages in thread
From: Scott J. Goldman @ 2026-04-27 19:55 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Alexander Graf, Phil Dennis-Jordan,
Roman Bolshakov, Philippe Mathieu-Daudé, Scott J. Goldman
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.
Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
---
Changes since v2:
- Use QEMU_CLOCK_VIRTUAL instead of QEMU_CLOCK_HOST so the timer
pauses with the VM and a halted vCPU isn't woken (or its IRQ
raised) while the user has stopped the guest. (Peter)
- Convert vtimer ticks to nanoseconds with muldiv64() to avoid
intermediate overflow. (Peter)
- Re-arm the WFI timer from hvf_vm_state_change() on the resume
transition so a halted vCPU on the migration destination is
woken when its vtimer expires (the QEMUTimer is per-instance
state and isn't migrated). (Peter)
v2: https://lore.kernel.org/qemu-devel/20260410055045.63001-1-scottjgo@gmail.com/
v1: https://lore.kernel.org/qemu-devel/20260410044726.61853-1-scottjgo@gmail.com/
include/system/hvf_int.h | 1 +
target/arm/hvf/hvf.c | 124 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 124 insertions(+), 1 deletion(-)
diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
index 2621164cb2..58fb865eba 100644
--- a/include/system/hvf_int.h
+++ b/include/system/hvf_int.h
@@ -48,6 +48,7 @@ struct AccelCPUState {
hv_vcpu_exit_t *exit;
bool vtimer_masked;
bool guest_debug_enabled;
+ struct QEMUTimer *wfi_timer;
#endif
};
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 678afe5c8e..a19d7a5e1f 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -28,6 +28,7 @@
#include "hw/core/boards.h"
#include "hw/core/irq.h"
#include "qemu/main-loop.h"
+#include "qemu/timer.h"
#include "system/cpus.h"
#include "arm-powerctl.h"
#include "target/arm/cpu.h"
@@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
#define TMR_CTL_IMASK (1 << 1)
#define TMR_CTL_ISTATUS (1 << 2)
+static void hvf_wfi_timer_cb(void *opaque);
+
static uint32_t chosen_ipa_bit_size;
typedef struct HVFVTimer {
@@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
{
hv_return_t ret;
+ timer_free(cpu->accel->wfi_timer);
+ cpu->accel->wfi_timer = NULL;
+
ret = hv_vcpu_destroy(cpu->accel->fd);
assert_hvf_ok(ret);
}
@@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
assert_hvf_ok(ret);
+ cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ hvf_wfi_timer_cb, cpu);
+
aarch64_add_sme_properties(OBJECT(cpu));
return 0;
}
@@ -2027,8 +2036,67 @@ static uint64_t hvf_vtimer_val_raw(void)
return mach_absolute_time() - hvf_state->vtimer_offset;
}
+static void hvf_wfi_timer_cb(void *opaque)
+{
+ CPUState *cpu = opaque;
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+
+ /*
+ * vtimer expired while the CPU was halted for WFI.
+ * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
+ * interrupt and mark as masked so hvf_sync_vtimer() will
+ * check and unmask when the guest handles it.
+ *
+ * The interrupt delivery chain (GIC -> cpu_interrupt ->
+ * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
+ */
+ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
+ cpu->accel->vtimer_masked = true;
+}
+
+/*
+ * Arm a host-side QEMU_CLOCK_VIRTUAL timer to fire when the guest's
+ * vtimer (CNTV_CVAL_EL0) is scheduled to expire. HVF only delivers
+ * HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which we won't
+ * call while the vCPU is halted, so we need this to wake the vCPU.
+ *
+ * QEMU_CLOCK_VIRTUAL pauses while the VM is stopped, which keeps the
+ * timer in lockstep with the guest's view of vtime across pause/resume.
+ *
+ * Caller must supply the current CNTV_CTL_EL0 and CNTV_CVAL_EL0 values,
+ * since the appropriate source (HVF vs. env) depends on context.
+ *
+ * Returns 0 if the timer was armed (or if the vtimer is disabled/masked
+ * and the vCPU should still halt waiting on another event), or -1 if
+ * the vtimer has already expired.
+ */
+static int hvf_arm_wfi_timer(CPUState *cpu, uint64_t ctl, uint64_t cval)
+{
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+ uint64_t now;
+ int64_t delta_ns;
+
+ if (!(ctl & TMR_CTL_ENABLE) || (ctl & TMR_CTL_IMASK)) {
+ return 0;
+ }
+
+ now = hvf_vtimer_val_raw();
+ if (cval <= now) {
+ return -1;
+ }
+
+ delta_ns = muldiv64(cval - now, NANOSECONDS_PER_SECOND,
+ arm_cpu->gt_cntfrq_hz);
+ timer_mod(cpu->accel->wfi_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns);
+ return 0;
+}
+
static int hvf_wfi(CPUState *cpu)
{
+ uint64_t ctl, cval;
+ hv_return_t r;
+
if (cpu_has_work(cpu)) {
/*
* Don't bother to go into our "low power state" if
@@ -2037,6 +2105,22 @@ static int hvf_wfi(CPUState *cpu)
return 0;
}
+ /*
+ * Read the vtimer state directly from HVF. We're on the vCPU thread,
+ * just exited from hv_vcpu_run(), so HVF holds the authoritative
+ * values and env may be stale.
+ */
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
+ assert_hvf_ok(r);
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0, &cval);
+ assert_hvf_ok(r);
+
+ if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
+ /* vtimer already expired, don't halt */
+ return 0;
+ }
+
+ cpu->halted = 1;
return EXCP_HLT;
}
@@ -2332,7 +2416,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
hv_return_t r;
if (cpu->halted) {
- return EXCP_HLT;
+ if (!cpu_has_work(cpu)) {
+ return EXCP_HLT;
+ }
+ cpu->halted = 0;
+ timer_del(cpu->accel->wfi_timer);
}
flush_cpu_state(cpu);
@@ -2376,11 +2464,45 @@ static const VMStateDescription vmstate_hvf_vtimer = {
static void hvf_vm_state_change(void *opaque, bool running, RunState state)
{
HVFVTimer *s = opaque;
+ CPUState *cpu;
if (running) {
/* Update vtimer offset on all CPUs */
hvf_state->vtimer_offset = mach_absolute_time() - s->vtimer_val;
cpu_synchronize_all_states();
+
+ /*
+ * After migration restore (or any resume), the wfi_timer is not
+ * scheduled on this QEMU instance, so re-arm it for any halted
+ * vCPU with a pending vtimer. For a non-migration resume the
+ * QEMU_CLOCK_VIRTUAL timer was already scheduled; recomputing the
+ * deadline produces the same value and is a harmless no-op.
+ *
+ * cpu_synchronize_all_states() above ensures env mirrors the
+ * authoritative vtimer state (whether that came from HVF or from
+ * the migration stream), so we can safely read it here from the
+ * iothread.
+ */
+ CPU_FOREACH(cpu) {
+ ARMCPU *arm_cpu;
+ uint64_t ctl, cval;
+
+ if (!cpu->accel || !cpu->halted) {
+ continue;
+ }
+
+ arm_cpu = ARM_CPU(cpu);
+ ctl = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].ctl;
+ cval = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].cval;
+
+ if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
+ /*
+ * vtimer already expired while we were paused; raise the
+ * IRQ now so the halted vCPU wakes up.
+ */
+ hvf_wfi_timer_cb(cpu);
+ }
+ }
} else {
/* Remember vtimer value on every pause */
s->vtimer_val = hvf_vtimer_val_raw();
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-27 19:55 ` [PATCH v3] " Scott J. Goldman
@ 2026-05-12 16:46 ` Scott J. Goldman
2026-05-12 19:18 ` Peter Maydell
2026-05-12 17:54 ` Mohamed Mediouni
2026-05-13 1:21 ` Mohamed Mediouni
2 siblings, 1 reply; 22+ messages in thread
From: Scott J. Goldman @ 2026-05-12 16:46 UTC (permalink / raw)
To: Peter Maydell
Cc: qemu-arm, Alexander Graf, Phil Dennis-Jordan, Roman Bolshakov,
Philippe Mathieu-Daudé, qemu-devel
On Mon Apr 27, 2026 at 3:55 PM EDT, Scott J. Goldman wrote:
> 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.
>
> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
> ---
> Changes since v2:
> - Use QEMU_CLOCK_VIRTUAL instead of QEMU_CLOCK_HOST so the timer
> pauses with the VM and a halted vCPU isn't woken (or its IRQ
> raised) while the user has stopped the guest. (Peter)
> - Convert vtimer ticks to nanoseconds with muldiv64() to avoid
> intermediate overflow. (Peter)
> - Re-arm the WFI timer from hvf_vm_state_change() on the resume
> transition so a halted vCPU on the migration destination is
> woken when its vtimer expires (the QEMUTimer is per-instance
> state and isn't migrated). (Peter)
> v2: https://lore.kernel.org/qemu-devel/20260410055045.63001-1-scottjgo@gmail.com/
> v1: https://lore.kernel.org/qemu-devel/20260410044726.61853-1-scottjgo@gmail.com/
>
> include/system/hvf_int.h | 1 +
> target/arm/hvf/hvf.c | 124 ++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 124 insertions(+), 1 deletion(-)
>
> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
> index 2621164cb2..58fb865eba 100644
> --- a/include/system/hvf_int.h
> +++ b/include/system/hvf_int.h
> @@ -48,6 +48,7 @@ struct AccelCPUState {
> hv_vcpu_exit_t *exit;
> bool vtimer_masked;
> bool guest_debug_enabled;
> + struct QEMUTimer *wfi_timer;
> #endif
> };
>
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index 678afe5c8e..a19d7a5e1f 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -28,6 +28,7 @@
> #include "hw/core/boards.h"
> #include "hw/core/irq.h"
> #include "qemu/main-loop.h"
> +#include "qemu/timer.h"
> #include "system/cpus.h"
> #include "arm-powerctl.h"
> #include "target/arm/cpu.h"
> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
> #define TMR_CTL_IMASK (1 << 1)
> #define TMR_CTL_ISTATUS (1 << 2)
>
> +static void hvf_wfi_timer_cb(void *opaque);
> +
> static uint32_t chosen_ipa_bit_size;
>
> typedef struct HVFVTimer {
> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
> {
> hv_return_t ret;
>
> + timer_free(cpu->accel->wfi_timer);
> + cpu->accel->wfi_timer = NULL;
> +
> ret = hv_vcpu_destroy(cpu->accel->fd);
> assert_hvf_ok(ret);
> }
> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
> assert_hvf_ok(ret);
>
> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
> + hvf_wfi_timer_cb, cpu);
> +
> aarch64_add_sme_properties(OBJECT(cpu));
> return 0;
> }
> @@ -2027,8 +2036,67 @@ static uint64_t hvf_vtimer_val_raw(void)
> return mach_absolute_time() - hvf_state->vtimer_offset;
> }
>
> +static void hvf_wfi_timer_cb(void *opaque)
> +{
> + CPUState *cpu = opaque;
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> +
> + /*
> + * vtimer expired while the CPU was halted for WFI.
> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
> + * interrupt and mark as masked so hvf_sync_vtimer() will
> + * check and unmask when the guest handles it.
> + *
> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
> + */
> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
> + cpu->accel->vtimer_masked = true;
> +}
> +
> +/*
> + * Arm a host-side QEMU_CLOCK_VIRTUAL timer to fire when the guest's
> + * vtimer (CNTV_CVAL_EL0) is scheduled to expire. HVF only delivers
> + * HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which we won't
> + * call while the vCPU is halted, so we need this to wake the vCPU.
> + *
> + * QEMU_CLOCK_VIRTUAL pauses while the VM is stopped, which keeps the
> + * timer in lockstep with the guest's view of vtime across pause/resume.
> + *
> + * Caller must supply the current CNTV_CTL_EL0 and CNTV_CVAL_EL0 values,
> + * since the appropriate source (HVF vs. env) depends on context.
> + *
> + * Returns 0 if the timer was armed (or if the vtimer is disabled/masked
> + * and the vCPU should still halt waiting on another event), or -1 if
> + * the vtimer has already expired.
> + */
> +static int hvf_arm_wfi_timer(CPUState *cpu, uint64_t ctl, uint64_t cval)
> +{
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> + uint64_t now;
> + int64_t delta_ns;
> +
> + if (!(ctl & TMR_CTL_ENABLE) || (ctl & TMR_CTL_IMASK)) {
> + return 0;
> + }
> +
> + now = hvf_vtimer_val_raw();
> + if (cval <= now) {
> + return -1;
> + }
> +
> + delta_ns = muldiv64(cval - now, NANOSECONDS_PER_SECOND,
> + arm_cpu->gt_cntfrq_hz);
> + timer_mod(cpu->accel->wfi_timer,
> + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns);
> + return 0;
> +}
> +
> static int hvf_wfi(CPUState *cpu)
> {
> + uint64_t ctl, cval;
> + hv_return_t r;
> +
> if (cpu_has_work(cpu)) {
> /*
> * Don't bother to go into our "low power state" if
> @@ -2037,6 +2105,22 @@ static int hvf_wfi(CPUState *cpu)
> return 0;
> }
>
> + /*
> + * Read the vtimer state directly from HVF. We're on the vCPU thread,
> + * just exited from hv_vcpu_run(), so HVF holds the authoritative
> + * values and env may be stale.
> + */
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
> + assert_hvf_ok(r);
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0, &cval);
> + assert_hvf_ok(r);
> +
> + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
> + /* vtimer already expired, don't halt */
> + return 0;
> + }
> +
> + cpu->halted = 1;
> return EXCP_HLT;
> }
>
> @@ -2332,7 +2416,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
> hv_return_t r;
>
> if (cpu->halted) {
> - return EXCP_HLT;
> + if (!cpu_has_work(cpu)) {
> + return EXCP_HLT;
> + }
> + cpu->halted = 0;
> + timer_del(cpu->accel->wfi_timer);
> }
>
> flush_cpu_state(cpu);
> @@ -2376,11 +2464,45 @@ static const VMStateDescription vmstate_hvf_vtimer = {
> static void hvf_vm_state_change(void *opaque, bool running, RunState state)
> {
> HVFVTimer *s = opaque;
> + CPUState *cpu;
>
> if (running) {
> /* Update vtimer offset on all CPUs */
> hvf_state->vtimer_offset = mach_absolute_time() - s->vtimer_val;
> cpu_synchronize_all_states();
> +
> + /*
> + * After migration restore (or any resume), the wfi_timer is not
> + * scheduled on this QEMU instance, so re-arm it for any halted
> + * vCPU with a pending vtimer. For a non-migration resume the
> + * QEMU_CLOCK_VIRTUAL timer was already scheduled; recomputing the
> + * deadline produces the same value and is a harmless no-op.
> + *
> + * cpu_synchronize_all_states() above ensures env mirrors the
> + * authoritative vtimer state (whether that came from HVF or from
> + * the migration stream), so we can safely read it here from the
> + * iothread.
> + */
> + CPU_FOREACH(cpu) {
> + ARMCPU *arm_cpu;
> + uint64_t ctl, cval;
> +
> + if (!cpu->accel || !cpu->halted) {
> + continue;
> + }
> +
> + arm_cpu = ARM_CPU(cpu);
> + ctl = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].ctl;
> + cval = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].cval;
> +
> + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
> + /*
> + * vtimer already expired while we were paused; raise the
> + * IRQ now so the halted vCPU wakes up.
> + */
> + hvf_wfi_timer_cb(cpu);
> + }
> + }
> } else {
> /* Remember vtimer value on every pause */
> s->vtimer_val = hvf_vtimer_val_raw();
Hi Peter--
Sorry to nag, just was wondering if you could take a look at this follow-up
to your earlier review comments in:
https://lore.kernel.org/qemu-devel/CAFEAcA9RHQ+7++=kLn2goJwcgzDnaqdkQtQBtxQ2Rw1-uiKY=g@mail.gmail.com/#t
It's a bit of an unfortunate regression, so I was hoping to get some form of
the fix in.
Additionally, re: migration under hvf being broken, it looks like one of my
fixes got merged, but there is one patch remaining here as well:
https://lore.kernel.org/qemu-devel/5e123304-628a-4da9-81ca-585498c809b6@linaro.org/
Thanks, and hope the ping is ok (just following the docs that say to retry
every 2 weeks)
-sjg
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-27 19:55 ` [PATCH v3] " Scott J. Goldman
2026-05-12 16:46 ` Scott J. Goldman
@ 2026-05-12 17:54 ` Mohamed Mediouni
2026-05-12 20:36 ` Scott J. Goldman
2026-05-13 1:21 ` Mohamed Mediouni
2 siblings, 1 reply; 22+ messages in thread
From: Mohamed Mediouni @ 2026-05-12 17:54 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, Peter Maydell, Alexander Graf,
Phil Dennis-Jordan, Roman Bolshakov, Philippe Mathieu-Daudé
> On 27. Apr 2026, at 21:55, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> 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.
>
> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
Hi,
A bit of a side note for reproducing this:
To reproduce this on current master, as far as I can tell you need either
-M kernel-irqchip=off or -M virt-11.0 or earlier. -M virt(-11.1) on master uses
the HVF vGIC path.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-12 16:46 ` Scott J. Goldman
@ 2026-05-12 19:18 ` Peter Maydell
0 siblings, 0 replies; 22+ messages in thread
From: Peter Maydell @ 2026-05-12 19:18 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-arm, Alexander Graf, Phil Dennis-Jordan, Roman Bolshakov,
Philippe Mathieu-Daudé, qemu-devel
On Tue, 12 May 2026 at 17:46, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> On Mon Apr 27, 2026 at 3:55 PM EDT, Scott J. Goldman wrote:
> > 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:
> Hi Peter--
>
> Sorry to nag, just was wondering if you could take a look at this follow-up
> to your earlier review comments in:
> https://lore.kernel.org/qemu-devel/CAFEAcA9RHQ+7++=kLn2goJwcgzDnaqdkQtQBtxQ2Rw1-uiKY=g@mail.gmail.com/#t
>
> It's a bit of an unfortunate regression, so I was hoping to get some form of
> the fix in.
I'm really hoping that somebody who knows and uses hvf will
review this (and your other hvf patchset)...
-- PMM
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-12 17:54 ` Mohamed Mediouni
@ 2026-05-12 20:36 ` Scott J. Goldman
2026-05-12 20:51 ` Mohamed Mediouni
0 siblings, 1 reply; 22+ messages in thread
From: Scott J. Goldman @ 2026-05-12 20:36 UTC (permalink / raw)
To: Mohamed Mediouni, Scott J. Goldman
Cc: qemu-devel, qemu-arm, Peter Maydell, Alexander Graf,
Phil Dennis-Jordan, Roman Bolshakov, Philippe Mathieu-Daudé
On Tue May 12, 2026 at 1:54 PM EDT, Mohamed Mediouni wrote:
>
>
>> On 27. Apr 2026, at 21:55, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>
>> 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.
>>
>> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
>> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
>
> Hi,
>
> A bit of a side note for reproducing this:
>
> To reproduce this on current master, as far as I can tell you need either
> -M kernel-irqchip=off or -M virt-11.0 or earlier. -M virt(-11.1) on master uses
> the HVF vGIC path.
Just wanted to confirm that I re-tested and it seems like you are
correct. This must have changed semi recently? I was just running with
`-M virt` before. Either way, it's good that the defaults seem to work
better now. Either way, I guess the fix still needs to go in for the
non vGIC path?
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-12 20:36 ` Scott J. Goldman
@ 2026-05-12 20:51 ` Mohamed Mediouni
0 siblings, 0 replies; 22+ messages in thread
From: Mohamed Mediouni @ 2026-05-12 20:51 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, Peter Maydell, Alexander Graf,
Phil Dennis-Jordan, Roman Bolshakov, Philippe Mathieu-Daudé
> On 12. May 2026, at 22:36, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> On Tue May 12, 2026 at 1:54 PM EDT, Mohamed Mediouni wrote:
>>
>>
>>> On 27. Apr 2026, at 21:55, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>>
>>> 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.
>>>
>>> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
>>> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
>>
>> Hi,
>>
>> A bit of a side note for reproducing this:
>>
>> To reproduce this on current master, as far as I can tell you need either
>> -M kernel-irqchip=off or -M virt-11.0 or earlier. -M virt(-11.1) on master uses
>> the HVF vGIC path.
>
> Just wanted to confirm that I re-tested and it seems like you are
> correct. This must have changed semi recently?
Hi,
It finally got merged last week.
> I was just running with `-M virt` before. Either way, it's good that the defaults seem to work
> better now. Either way, I guess the fix still needs to go in for the
> non vGIC path?
>
Yes. I’ll do a review. This patch is also a qemu-stable candidate.
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-04-27 19:55 ` [PATCH v3] " Scott J. Goldman
2026-05-12 16:46 ` Scott J. Goldman
2026-05-12 17:54 ` Mohamed Mediouni
@ 2026-05-13 1:21 ` Mohamed Mediouni
2026-05-13 2:21 ` [PATCH v11.1+ v4] " Scott J. Goldman
2026-05-13 2:25 ` [PATCH v3] " Scott J. Goldman
2 siblings, 2 replies; 22+ messages in thread
From: Mohamed Mediouni @ 2026-05-13 1:21 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, Peter Maydell, Alexander Graf,
Phil Dennis-Jordan, Roman Bolshakov, Philippe Mathieu-Daudé
> On 27. Apr 2026, at 21:55, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> 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.
>
> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
> ---
> Changes since v2:
> - Use QEMU_CLOCK_VIRTUAL instead of QEMU_CLOCK_HOST so the timer
> pauses with the VM and a halted vCPU isn't woken (or its IRQ
> raised) while the user has stopped the guest. (Peter)
> - Convert vtimer ticks to nanoseconds with muldiv64() to avoid
> intermediate overflow. (Peter)
> - Re-arm the WFI timer from hvf_vm_state_change() on the resume
> transition so a halted vCPU on the migration destination is
> woken when its vtimer expires (the QEMUTimer is per-instance
> state and isn't migrated). (Peter)
> v2: https://lore.kernel.org/qemu-devel/20260410055045.63001-1-scottjgo@gmail.com/
> v1: https://lore.kernel.org/qemu-devel/20260410044726.61853-1-scottjgo@gmail.com/
For QEMU 11.0 (for backporting to stable):
Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
For QEMU 11.1:
Adding some checks for !hvf_irqchip_in_kernel() needed
but can do them on my side if you prefer.
Looks ready apart from that bit.
>
> include/system/hvf_int.h | 1 +
> target/arm/hvf/hvf.c | 124 ++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 124 insertions(+), 1 deletion(-)
>
> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
> index 2621164cb2..58fb865eba 100644
> --- a/include/system/hvf_int.h
> +++ b/include/system/hvf_int.h
> @@ -48,6 +48,7 @@ struct AccelCPUState {
> hv_vcpu_exit_t *exit;
> bool vtimer_masked;
> bool guest_debug_enabled;
> + struct QEMUTimer *wfi_timer;
> #endif
> };
>
> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
> index 678afe5c8e..a19d7a5e1f 100644
> --- a/target/arm/hvf/hvf.c
> +++ b/target/arm/hvf/hvf.c
> @@ -28,6 +28,7 @@
> #include "hw/core/boards.h"
> #include "hw/core/irq.h"
> #include "qemu/main-loop.h"
> +#include "qemu/timer.h"
> #include "system/cpus.h"
> #include "arm-powerctl.h"
> #include "target/arm/cpu.h"
> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
> #define TMR_CTL_IMASK (1 << 1)
> #define TMR_CTL_ISTATUS (1 << 2)
>
> +static void hvf_wfi_timer_cb(void *opaque);
> +
> static uint32_t chosen_ipa_bit_size;
>
> typedef struct HVFVTimer {
> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
> {
> hv_return_t ret;
>
> + timer_free(cpu->accel->wfi_timer);
> + cpu->accel->wfi_timer = NULL;
> +
> ret = hv_vcpu_destroy(cpu->accel->fd);
> assert_hvf_ok(ret);
> }
> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
> assert_hvf_ok(ret);
>
> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
> + hvf_wfi_timer_cb, cpu);
> +
adding !hvf_irqchip_in_kernel()
> aarch64_add_sme_properties(OBJECT(cpu));
> return 0;
> }
> @@ -2027,8 +2036,67 @@ static uint64_t hvf_vtimer_val_raw(void)
> return mach_absolute_time() - hvf_state->vtimer_offset;
> }
>
> +static void hvf_wfi_timer_cb(void *opaque)
> +{
> + CPUState *cpu = opaque;
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> +
> + /*
> + * vtimer expired while the CPU was halted for WFI.
> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
> + * interrupt and mark as masked so hvf_sync_vtimer() will
> + * check and unmask when the guest handles it.
> + *
> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
> + */
> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
> + cpu->accel->vtimer_masked = true;
> +}
> +
> +/*
> + * Arm a host-side QEMU_CLOCK_VIRTUAL timer to fire when the guest's
> + * vtimer (CNTV_CVAL_EL0) is scheduled to expire. HVF only delivers
> + * HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which we won't
> + * call while the vCPU is halted, so we need this to wake the vCPU.
> + *
> + * QEMU_CLOCK_VIRTUAL pauses while the VM is stopped, which keeps the
> + * timer in lockstep with the guest's view of vtime across pause/resume.
> + *
> + * Caller must supply the current CNTV_CTL_EL0 and CNTV_CVAL_EL0 values,
> + * since the appropriate source (HVF vs. env) depends on context.
> + *
> + * Returns 0 if the timer was armed (or if the vtimer is disabled/masked
> + * and the vCPU should still halt waiting on another event), or -1 if
> + * the vtimer has already expired.
> + */
> +static int hvf_arm_wfi_timer(CPUState *cpu, uint64_t ctl, uint64_t cval)
> +{
> + ARMCPU *arm_cpu = ARM_CPU(cpu);
> + uint64_t now;
> + int64_t delta_ns;
> +
> + if (!(ctl & TMR_CTL_ENABLE) || (ctl & TMR_CTL_IMASK)) {
> + return 0;
> + }
> +
> + now = hvf_vtimer_val_raw();
> + if (cval <= now) {
> + return -1;
> + }
> +
> + delta_ns = muldiv64(cval - now, NANOSECONDS_PER_SECOND,
> + arm_cpu->gt_cntfrq_hz);
> + timer_mod(cpu->accel->wfi_timer,
> + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns);
> + return 0;
> +}
> +
> static int hvf_wfi(CPUState *cpu)
> {
> + uint64_t ctl, cval;
> + hv_return_t r;
> +
> if (cpu_has_work(cpu)) {
> /*
> * Don't bother to go into our "low power state" if
> @@ -2037,6 +2105,22 @@ static int hvf_wfi(CPUState *cpu)
> return 0;
> }
>
> + /*
> + * Read the vtimer state directly from HVF. We're on the vCPU thread,
> + * just exited from hv_vcpu_run(), so HVF holds the authoritative
> + * values and env may be stale.
> + */
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
> + assert_hvf_ok(r);
> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0, &cval);
> + assert_hvf_ok(r);
> +
> + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
> + /* vtimer already expired, don't halt */
> + return 0;
> + }
> +
> + cpu->halted = 1;
> return EXCP_HLT;
> }
>
> @@ -2332,7 +2416,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
> hv_return_t r;
>
> if (cpu->halted) {
> - return EXCP_HLT;
> + if (!cpu_has_work(cpu)) {
> + return EXCP_HLT;
> + }
> + cpu->halted = 0;
> + timer_del(cpu->accel->wfi_timer);
!hvf_irqchip_in_kernel(), we shouldn’t have the wfi timer otherwise
> }
>
> flush_cpu_state(cpu);
> @@ -2376,11 +2464,45 @@ static const VMStateDescription vmstate_hvf_vtimer = {
> static void hvf_vm_state_change(void *opaque, bool running, RunState state)
> {
> HVFVTimer *s = opaque;
> + CPUState *cpu;
>
> if (running) {
> /* Update vtimer offset on all CPUs */
> hvf_state->vtimer_offset = mach_absolute_time() - s->vtimer_val;
> cpu_synchronize_all_states();
> +
> + /*
> + * After migration restore (or any resume), the wfi_timer is not
> + * scheduled on this QEMU instance, so re-arm it for any halted
> + * vCPU with a pending vtimer. For a non-migration resume the
> + * QEMU_CLOCK_VIRTUAL timer was already scheduled; recomputing the
> + * deadline produces the same value and is a harmless no-op.
> + *
> + * cpu_synchronize_all_states() above ensures env mirrors the
> + * authoritative vtimer state (whether that came from HVF or from
> + * the migration stream), so we can safely read it here from the
> + * iothread.
> + */
This should be gated behind !hvf_irqchip_in_kernel()
> + CPU_FOREACH(cpu) {
> + ARMCPU *arm_cpu;
> + uint64_t ctl, cval;
> +
> + if (!cpu->accel || !cpu->halted) {
> + continue;
> + }
> +
> + arm_cpu = ARM_CPU(cpu);
> + ctl = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].ctl;
> + cval = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].cval;
> +
> + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
> + /*
> + * vtimer already expired while we were paused; raise the
> + * IRQ now so the halted vCPU wakes up.
> + */
> + hvf_wfi_timer_cb(cpu);
> + }
> + }
> } else {
> /* Remember vtimer value on every pause */
> s->vtimer_val = hvf_vtimer_val_raw();
> --
> 2.50.1 (Apple Git-155)
>
>
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH v11.1+ v4] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-13 1:21 ` Mohamed Mediouni
@ 2026-05-13 2:21 ` Scott J. Goldman
2026-05-15 10:39 ` Peter Maydell
2026-05-13 2:25 ` [PATCH v3] " Scott J. Goldman
1 sibling, 1 reply; 22+ messages in thread
From: Scott J. Goldman @ 2026-05-13 2:21 UTC (permalink / raw)
To: Mohamed Mediouni
Cc: Roman Bolshakov, Phil Dennis-Jordan, Alexander Graf,
Peter Maydell, qemu-arm, qemu-devel, Scott J. Goldman
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.
Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
---
v4: Gate all wfi_timer handling on !hvf_irqchip_in_kernel() (per
Mohamed's review). This version is only intended for the `master`
branch, while v3 can be applied to the 11.0 stable tree.
v3: Re-arm the wfi_timer from hvf_vm_state_change() so migration restore
wakes a halted vCPU.
include/system/hvf_int.h | 1 +
target/arm/hvf/hvf.c | 140 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 140 insertions(+), 1 deletion(-)
diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
index ad7d375109..d5eaf26dda 100644
--- a/include/system/hvf_int.h
+++ b/include/system/hvf_int.h
@@ -48,6 +48,7 @@ struct AccelCPUState {
hv_vcpu_exit_t *exit;
bool vtimer_masked;
bool guest_debug_enabled;
+ struct QEMUTimer *wfi_timer;
#endif
};
diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
index 5a1718f7f9..0666175973 100644
--- a/target/arm/hvf/hvf.c
+++ b/target/arm/hvf/hvf.c
@@ -29,6 +29,7 @@
#include "hw/core/irq.h"
#include "hw/arm/virt.h"
#include "qemu/main-loop.h"
+#include "qemu/timer.h"
#include "system/cpus.h"
#include "arm-powerctl.h"
#include "target/arm/cpu.h"
@@ -308,6 +309,8 @@ void hvf_arm_init_debug(void)
#define TMR_CTL_IMASK (1 << 1)
#define TMR_CTL_ISTATUS (1 << 2)
+static void hvf_wfi_timer_cb(void *opaque);
+
static uint32_t chosen_ipa_bit_size;
typedef struct HVFVTimer {
@@ -1296,6 +1299,11 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
{
hv_return_t ret;
+ if (!hvf_irqchip_in_kernel()) {
+ timer_free(cpu->accel->wfi_timer);
+ cpu->accel->wfi_timer = NULL;
+ }
+
ret = hv_vcpu_destroy(cpu->accel->fd);
assert_hvf_ok(ret);
}
@@ -1494,6 +1502,11 @@ int hvf_arch_init_vcpu(CPUState *cpu)
arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
assert_hvf_ok(ret);
+ if (!hvf_irqchip_in_kernel()) {
+ cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ hvf_wfi_timer_cb, cpu);
+ }
+
aarch64_add_sme_properties(OBJECT(cpu));
return 0;
}
@@ -2201,6 +2214,62 @@ static uint64_t hvf_vtimer_val_raw(void)
return mach_absolute_time() - hvf_state->vtimer_offset;
}
+static void hvf_wfi_timer_cb(void *opaque)
+{
+ CPUState *cpu = opaque;
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+
+ /*
+ * vtimer expired while the CPU was halted for WFI.
+ * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
+ * interrupt and mark as masked so hvf_sync_vtimer() will
+ * check and unmask when the guest handles it.
+ *
+ * The interrupt delivery chain (GIC -> cpu_interrupt ->
+ * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
+ */
+ qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
+ cpu->accel->vtimer_masked = true;
+}
+
+/*
+ * Arm a host-side QEMU_CLOCK_VIRTUAL timer to fire when the guest's
+ * vtimer (CNTV_CVAL_EL0) is scheduled to expire. HVF only delivers
+ * HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which we won't
+ * call while the vCPU is halted, so we need this to wake the vCPU.
+ *
+ * QEMU_CLOCK_VIRTUAL pauses while the VM is stopped, which keeps the
+ * timer in lockstep with the guest's view of vtime across pause/resume.
+ *
+ * Caller must supply the current CNTV_CTL_EL0 and CNTV_CVAL_EL0 values,
+ * since the appropriate source (HVF vs. env) depends on context.
+ *
+ * Returns 0 if the timer was armed (or if the vtimer is disabled/masked
+ * and the vCPU should still halt waiting on another event), or -1 if
+ * the vtimer has already expired.
+ */
+static int hvf_arm_wfi_timer(CPUState *cpu, uint64_t ctl, uint64_t cval)
+{
+ ARMCPU *arm_cpu = ARM_CPU(cpu);
+ uint64_t now;
+ int64_t delta_ns;
+
+ if (!(ctl & TMR_CTL_ENABLE) || (ctl & TMR_CTL_IMASK)) {
+ return 0;
+ }
+
+ now = hvf_vtimer_val_raw();
+ if (cval <= now) {
+ return -1;
+ }
+
+ delta_ns = muldiv64(cval - now, NANOSECONDS_PER_SECOND,
+ arm_cpu->gt_cntfrq_hz);
+ timer_mod(cpu->accel->wfi_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns);
+ return 0;
+}
+
static int hvf_wfi(CPUState *cpu)
{
if (cpu_has_work(cpu)) {
@@ -2211,6 +2280,29 @@ static int hvf_wfi(CPUState *cpu)
return 0;
}
+ if (!hvf_irqchip_in_kernel()) {
+ uint64_t ctl, cval;
+ hv_return_t r;
+
+ /*
+ * Read the vtimer state directly from HVF. We're on the vCPU
+ * thread, just exited from hv_vcpu_run(), so HVF holds the
+ * authoritative values and env may be stale.
+ */
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0,
+ &ctl);
+ assert_hvf_ok(r);
+ r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0,
+ &cval);
+ assert_hvf_ok(r);
+
+ if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
+ /* vtimer already expired, don't halt */
+ return 0;
+ }
+ }
+
+ cpu->halted = 1;
return EXCP_HLT;
}
@@ -2509,7 +2601,13 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
hv_return_t r;
if (cpu->halted) {
- return EXCP_HLT;
+ if (!cpu_has_work(cpu)) {
+ return EXCP_HLT;
+ }
+ cpu->halted = 0;
+ if (!hvf_irqchip_in_kernel()) {
+ timer_del(cpu->accel->wfi_timer);
+ }
}
flush_cpu_state(cpu);
@@ -2558,6 +2656,46 @@ static void hvf_vm_state_change(void *opaque, bool running, RunState state)
/* Update vtimer offset on all CPUs */
hvf_state->vtimer_offset = mach_absolute_time() - s->vtimer_val;
cpu_synchronize_all_states();
+
+ /*
+ * After migration restore (or any resume), the wfi_timer is not
+ * scheduled on this QEMU instance, so re-arm it for any halted
+ * vCPU with a pending vtimer. For a non-migration resume the
+ * QEMU_CLOCK_VIRTUAL timer was already scheduled; recomputing the
+ * deadline produces the same value and is a harmless no-op.
+ *
+ * cpu_synchronize_all_states() above ensures env mirrors the
+ * authoritative vtimer state (whether that came from HVF or from
+ * the migration stream), so we can safely read it here from the
+ * iothread.
+ *
+ * Only applies when we own the wfi_timer; with an in-kernel vGIC
+ * the timer is never allocated and HVF handles vtimer wake-ups.
+ */
+ if (!hvf_irqchip_in_kernel()) {
+ CPUState *cpu;
+
+ CPU_FOREACH(cpu) {
+ ARMCPU *arm_cpu;
+ uint64_t ctl, cval;
+
+ if (!cpu->accel || !cpu->halted) {
+ continue;
+ }
+
+ arm_cpu = ARM_CPU(cpu);
+ ctl = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].ctl;
+ cval = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].cval;
+
+ if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
+ /*
+ * vtimer already expired while we were paused; raise
+ * the IRQ now so the halted vCPU wakes up.
+ */
+ hvf_wfi_timer_cb(cpu);
+ }
+ }
+ }
} else {
/* Remember vtimer value on every pause */
s->vtimer_val = hvf_vtimer_val_raw();
--
2.50.1 (Apple Git-155)
^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-13 1:21 ` Mohamed Mediouni
2026-05-13 2:21 ` [PATCH v11.1+ v4] " Scott J. Goldman
@ 2026-05-13 2:25 ` Scott J. Goldman
2026-05-13 7:14 ` Mohamed Mediouni
1 sibling, 1 reply; 22+ messages in thread
From: Scott J. Goldman @ 2026-05-13 2:25 UTC (permalink / raw)
To: Mohamed Mediouni, Scott J. Goldman
Cc: qemu-devel, qemu-arm, Peter Maydell, Alexander Graf,
Phil Dennis-Jordan, Roman Bolshakov, Philippe Mathieu-Daudé
On Tue May 12, 2026 at 9:21 PM EDT, Mohamed Mediouni wrote:
>
>> On 27. Apr 2026, at 21:55, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>
>> 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.
>>
>> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
>> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
>> ---
>> Changes since v2:
>> - Use QEMU_CLOCK_VIRTUAL instead of QEMU_CLOCK_HOST so the timer
>> pauses with the VM and a halted vCPU isn't woken (or its IRQ
>> raised) while the user has stopped the guest. (Peter)
>> - Convert vtimer ticks to nanoseconds with muldiv64() to avoid
>> intermediate overflow. (Peter)
>> - Re-arm the WFI timer from hvf_vm_state_change() on the resume
>> transition so a halted vCPU on the migration destination is
>> woken when its vtimer expires (the QEMUTimer is per-instance
>> state and isn't migrated). (Peter)
>> v2: https://lore.kernel.org/qemu-devel/20260410055045.63001-1-scottjgo@gmail.com/
>> v1: https://lore.kernel.org/qemu-devel/20260410044726.61853-1-scottjgo@gmail.com/
>
> For QEMU 11.0 (for backporting to stable):
>
> Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
>
> For QEMU 11.1:
>
> Adding some checks for !hvf_irqchip_in_kernel() needed
> but can do them on my side if you prefer.
>
> Looks ready apart from that bit.
Hi Mohamed,
TFTR - I sent a follow-up patch that gates the wfi timer as you requested.
For applying this v3 version to the stable tree, is there anyone else I
should ping? I'm not very familiar with the process.
Thanks!
-sjg
>
>>
>> include/system/hvf_int.h | 1 +
>> target/arm/hvf/hvf.c | 124 ++++++++++++++++++++++++++++++++++++++-
>> 2 files changed, 124 insertions(+), 1 deletion(-)
>>
>> diff --git a/include/system/hvf_int.h b/include/system/hvf_int.h
>> index 2621164cb2..58fb865eba 100644
>> --- a/include/system/hvf_int.h
>> +++ b/include/system/hvf_int.h
>> @@ -48,6 +48,7 @@ struct AccelCPUState {
>> hv_vcpu_exit_t *exit;
>> bool vtimer_masked;
>> bool guest_debug_enabled;
>> + struct QEMUTimer *wfi_timer;
>> #endif
>> };
>>
>> diff --git a/target/arm/hvf/hvf.c b/target/arm/hvf/hvf.c
>> index 678afe5c8e..a19d7a5e1f 100644
>> --- a/target/arm/hvf/hvf.c
>> +++ b/target/arm/hvf/hvf.c
>> @@ -28,6 +28,7 @@
>> #include "hw/core/boards.h"
>> #include "hw/core/irq.h"
>> #include "qemu/main-loop.h"
>> +#include "qemu/timer.h"
>> #include "system/cpus.h"
>> #include "arm-powerctl.h"
>> #include "target/arm/cpu.h"
>> @@ -301,6 +302,8 @@ void hvf_arm_init_debug(void)
>> #define TMR_CTL_IMASK (1 << 1)
>> #define TMR_CTL_ISTATUS (1 << 2)
>>
>> +static void hvf_wfi_timer_cb(void *opaque);
>> +
>> static uint32_t chosen_ipa_bit_size;
>>
>> typedef struct HVFVTimer {
>> @@ -1214,6 +1217,9 @@ void hvf_arch_vcpu_destroy(CPUState *cpu)
>> {
>> hv_return_t ret;
>>
>> + timer_free(cpu->accel->wfi_timer);
>> + cpu->accel->wfi_timer = NULL;
>> +
>> ret = hv_vcpu_destroy(cpu->accel->fd);
>> assert_hvf_ok(ret);
>> }
>> @@ -1352,6 +1358,9 @@ int hvf_arch_init_vcpu(CPUState *cpu)
>> arm_cpu->isar.idregs[ID_AA64MMFR0_EL1_IDX]);
>> assert_hvf_ok(ret);
>>
>> + cpu->accel->wfi_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
>> + hvf_wfi_timer_cb, cpu);
>> +
> adding !hvf_irqchip_in_kernel()
>
>> aarch64_add_sme_properties(OBJECT(cpu));
>> return 0;
>> }
>> @@ -2027,8 +2036,67 @@ static uint64_t hvf_vtimer_val_raw(void)
>> return mach_absolute_time() - hvf_state->vtimer_offset;
>> }
>>
>> +static void hvf_wfi_timer_cb(void *opaque)
>> +{
>> + CPUState *cpu = opaque;
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> +
>> + /*
>> + * vtimer expired while the CPU was halted for WFI.
>> + * Mirror HV_EXIT_REASON_VTIMER_ACTIVATED: raise the vtimer
>> + * interrupt and mark as masked so hvf_sync_vtimer() will
>> + * check and unmask when the guest handles it.
>> + *
>> + * The interrupt delivery chain (GIC -> cpu_interrupt ->
>> + * qemu_cpu_kick) wakes the vCPU thread from halt_cond.
>> + */
>> + qemu_set_irq(arm_cpu->gt_timer_outputs[GTIMER_VIRT], 1);
>> + cpu->accel->vtimer_masked = true;
>> +}
>> +
>> +/*
>> + * Arm a host-side QEMU_CLOCK_VIRTUAL timer to fire when the guest's
>> + * vtimer (CNTV_CVAL_EL0) is scheduled to expire. HVF only delivers
>> + * HV_EXIT_REASON_VTIMER_ACTIVATED during hv_vcpu_run(), which we won't
>> + * call while the vCPU is halted, so we need this to wake the vCPU.
>> + *
>> + * QEMU_CLOCK_VIRTUAL pauses while the VM is stopped, which keeps the
>> + * timer in lockstep with the guest's view of vtime across pause/resume.
>> + *
>> + * Caller must supply the current CNTV_CTL_EL0 and CNTV_CVAL_EL0 values,
>> + * since the appropriate source (HVF vs. env) depends on context.
>> + *
>> + * Returns 0 if the timer was armed (or if the vtimer is disabled/masked
>> + * and the vCPU should still halt waiting on another event), or -1 if
>> + * the vtimer has already expired.
>> + */
>> +static int hvf_arm_wfi_timer(CPUState *cpu, uint64_t ctl, uint64_t cval)
>> +{
>> + ARMCPU *arm_cpu = ARM_CPU(cpu);
>> + uint64_t now;
>> + int64_t delta_ns;
>> +
>> + if (!(ctl & TMR_CTL_ENABLE) || (ctl & TMR_CTL_IMASK)) {
>> + return 0;
>> + }
>> +
>> + now = hvf_vtimer_val_raw();
>> + if (cval <= now) {
>> + return -1;
>> + }
>> +
>> + delta_ns = muldiv64(cval - now, NANOSECONDS_PER_SECOND,
>> + arm_cpu->gt_cntfrq_hz);
>> + timer_mod(cpu->accel->wfi_timer,
>> + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + delta_ns);
>> + return 0;
>> +}
>> +
>> static int hvf_wfi(CPUState *cpu)
>> {
>> + uint64_t ctl, cval;
>> + hv_return_t r;
>> +
>> if (cpu_has_work(cpu)) {
>> /*
>> * Don't bother to go into our "low power state" if
>> @@ -2037,6 +2105,22 @@ static int hvf_wfi(CPUState *cpu)
>> return 0;
>> }
>>
>> + /*
>> + * Read the vtimer state directly from HVF. We're on the vCPU thread,
>> + * just exited from hv_vcpu_run(), so HVF holds the authoritative
>> + * values and env may be stale.
>> + */
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CTL_EL0, &ctl);
>> + assert_hvf_ok(r);
>> + r = hv_vcpu_get_sys_reg(cpu->accel->fd, HV_SYS_REG_CNTV_CVAL_EL0, &cval);
>> + assert_hvf_ok(r);
>> +
>> + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
>> + /* vtimer already expired, don't halt */
>> + return 0;
>> + }
>> +
>> + cpu->halted = 1;
>> return EXCP_HLT;
>> }
>>
>> @@ -2332,7 +2416,11 @@ int hvf_arch_vcpu_exec(CPUState *cpu)
>> hv_return_t r;
>>
>> if (cpu->halted) {
>> - return EXCP_HLT;
>> + if (!cpu_has_work(cpu)) {
>> + return EXCP_HLT;
>> + }
>> + cpu->halted = 0;
>> + timer_del(cpu->accel->wfi_timer);
> !hvf_irqchip_in_kernel(), we shouldn’t have the wfi timer otherwise
>> }
>>
>> flush_cpu_state(cpu);
>> @@ -2376,11 +2464,45 @@ static const VMStateDescription vmstate_hvf_vtimer = {
>> static void hvf_vm_state_change(void *opaque, bool running, RunState state)
>> {
>> HVFVTimer *s = opaque;
>> + CPUState *cpu;
>>
>> if (running) {
>> /* Update vtimer offset on all CPUs */
>> hvf_state->vtimer_offset = mach_absolute_time() - s->vtimer_val;
>> cpu_synchronize_all_states();
>> +
>> + /*
>> + * After migration restore (or any resume), the wfi_timer is not
>> + * scheduled on this QEMU instance, so re-arm it for any halted
>> + * vCPU with a pending vtimer. For a non-migration resume the
>> + * QEMU_CLOCK_VIRTUAL timer was already scheduled; recomputing the
>> + * deadline produces the same value and is a harmless no-op.
>> + *
>> + * cpu_synchronize_all_states() above ensures env mirrors the
>> + * authoritative vtimer state (whether that came from HVF or from
>> + * the migration stream), so we can safely read it here from the
>> + * iothread.
>> + */
>
> This should be gated behind !hvf_irqchip_in_kernel()
>
>> + CPU_FOREACH(cpu) {
>> + ARMCPU *arm_cpu;
>> + uint64_t ctl, cval;
>> +
>> + if (!cpu->accel || !cpu->halted) {
>> + continue;
>> + }
>> +
>> + arm_cpu = ARM_CPU(cpu);
>> + ctl = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].ctl;
>> + cval = arm_cpu->env.cp15.c14_timer[GTIMER_VIRT].cval;
>> +
>> + if (hvf_arm_wfi_timer(cpu, ctl, cval) < 0) {
>> + /*
>> + * vtimer already expired while we were paused; raise the
>> + * IRQ now so the halted vCPU wakes up.
>> + */
>> + hvf_wfi_timer_cb(cpu);
>> + }
>> + }
>> } else {
>> /* Remember vtimer value on every pause */
>> s->vtimer_val = hvf_vtimer_val_raw();
>> --
>> 2.50.1 (Apple Git-155)
>>
>>
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v3] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-13 2:25 ` [PATCH v3] " Scott J. Goldman
@ 2026-05-13 7:14 ` Mohamed Mediouni
0 siblings, 0 replies; 22+ messages in thread
From: Mohamed Mediouni @ 2026-05-13 7:14 UTC (permalink / raw)
To: Scott J. Goldman
Cc: qemu-devel, qemu-arm, Peter Maydell, Alexander Graf,
Phil Dennis-Jordan, Roman Bolshakov, Philippe Mathieu-Daudé,
qemu-stable
> On 13. May 2026, at 04:25, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> On Tue May 12, 2026 at 9:21 PM EDT, Mohamed Mediouni wrote:
>>
>>> On 27. Apr 2026, at 21:55, Scott J. Goldman <scottjgo@gmail.com> wrote:
>>>
>>> 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.
>>>
>>> Fixes: b5f8f77271 ("accel/hvf: Implement WFI without using pselect()")
>>> Signed-off-by: Scott J. Goldman <scottjgo@gmail.com>
>>> ---
>>> Changes since v2:
>>> - Use QEMU_CLOCK_VIRTUAL instead of QEMU_CLOCK_HOST so the timer
>>> pauses with the VM and a halted vCPU isn't woken (or its IRQ
>>> raised) while the user has stopped the guest. (Peter)
>>> - Convert vtimer ticks to nanoseconds with muldiv64() to avoid
>>> intermediate overflow. (Peter)
>>> - Re-arm the WFI timer from hvf_vm_state_change() on the resume
>>> transition so a halted vCPU on the migration destination is
>>> woken when its vtimer expires (the QEMUTimer is per-instance
>>> state and isn't migrated). (Peter)
>>> v2: https://lore.kernel.org/qemu-devel/20260410055045.63001-1-scottjgo@gmail.com/
>>> v1: https://lore.kernel.org/qemu-devel/20260410044726.61853-1-scottjgo@gmail.com/
>>
>> For QEMU 11.0 (for backporting to stable):
>>
>> Reviewed-by: Mohamed Mediouni <mohamed@unpredictable.fr>
>>
>> For QEMU 11.1:
>>
>> Adding some checks for !hvf_irqchip_in_kernel() needed
>> but can do them on my side if you prefer.
>>
>> Looks ready apart from that bit.
>
> Hi Mohamed,
>
> TFTR - I sent a follow-up patch that gates the wfi timer as you requested.
> For applying this v3 version to the stable tree, is there anyone else I
> should ping? I'm not very familiar with the process.
>
> Thanks!
> -sjg
>
>
Hi,
v3 of this patch is for QEMU 11.0 qemu-stable and v4 is for QEMU 11.1.
A catch is that this isn’t in master yet though.
This regression doesn’t apply to QEMU 10.2 or earlier.
Could this regression fix be included into QEMU 11.0.1?
(with qemu-stable@ in cc)
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH v11.1+ v4] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning
2026-05-13 2:21 ` [PATCH v11.1+ v4] " Scott J. Goldman
@ 2026-05-15 10:39 ` Peter Maydell
0 siblings, 0 replies; 22+ messages in thread
From: Peter Maydell @ 2026-05-15 10:39 UTC (permalink / raw)
To: Scott J. Goldman
Cc: Mohamed Mediouni, Roman Bolshakov, Phil Dennis-Jordan,
Alexander Graf, qemu-arm, qemu-devel
On Wed, 13 May 2026 at 03:21, Scott J. Goldman <scottjgo@gmail.com> wrote:
>
> 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.
I've applied this to target-arm.next for 11.1; thanks.
-- PMM
^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2026-05-15 10:40 UTC | newest]
Thread overview: 22+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-10 4:47 [PATCH] target/arm/hvf: Fix WFI halting to stop idle vCPU spinning Scott J. Goldman
2026-04-10 5:06 ` Mohamed Mediouni
2026-04-10 5:28 ` Scott J. Goldman
2026-04-10 5:50 ` [PATCH v2] " Scott J. Goldman
2026-04-10 6:18 ` Mohamed Mediouni
2026-04-16 21:20 ` Scott J. Goldman
2026-04-17 9:57 ` Philippe Mathieu-Daudé
2026-04-17 20:30 ` Scott J. Goldman
2026-04-21 23:24 ` Scott J. Goldman
2026-04-27 11:15 ` Peter Maydell
2026-04-27 18:42 ` Scott J. Goldman
2026-04-27 19:55 ` [PATCH v3] " Scott J. Goldman
2026-05-12 16:46 ` Scott J. Goldman
2026-05-12 19:18 ` Peter Maydell
2026-05-12 17:54 ` Mohamed Mediouni
2026-05-12 20:36 ` Scott J. Goldman
2026-05-12 20:51 ` Mohamed Mediouni
2026-05-13 1:21 ` Mohamed Mediouni
2026-05-13 2:21 ` [PATCH v11.1+ v4] " Scott J. Goldman
2026-05-15 10:39 ` Peter Maydell
2026-05-13 2:25 ` [PATCH v3] " Scott J. Goldman
2026-05-13 7:14 ` Mohamed Mediouni
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox