@samitouri / QOSamiQemu / commits / da9b86c35f

target/arm: implement WFET

Now we have the event stream and SEV/SEVL implemented we can finally enable WFET for Aarch64. To avoid issues with QEMU's incomplete ldst exclusive handling causing potential deadlocks in common WFE enabled locking patterns we take advantage of the architectures flexibility and treat being in the exclusive region as a reason to exit. Reviewed-by: Richard Henderson <richard.henderson@linaro.org> Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-id: 20260624103049.884930-8-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

Alex Bennée committed Jun 24, 2026 at 11:30 UTC da9b86c35fa8690091451ddfb2d89c582cc32d87
3 files changed +103 -7
target/arm/tcg/helper-defs.h
+1
@@ -56,6 +56,7 @@ DEF_HELPER_1(setend, void, env)
56 DEF_HELPER_2(wfi, void, env, i32)
57 DEF_HELPER_2(wfe, void, env, i32)
58 DEF_HELPER_2(wfit, void, env, i32)
59 +DEF_HELPER_2(wfet, void, env, i32)
60 DEF_HELPER_1(yield, void, env)
61 DEF_HELPER_1(pre_hvc, void, env)
62 DEF_HELPER_2(pre_smc, void, env, i32)
target/arm/tcg/op_helper.c
+94
@@ -641,6 +641,100 @@ void HELPER(wfe)(CPUARMState *env, uint32_t insn_len)
641 #endif
642 }
643
644 +void HELPER(wfet)(CPUARMState *env, uint32_t rd)
645 +{
646 +#ifdef CONFIG_USER_ONLY
647 + /*
648 + * As for WFIT make it NOP here, because trying to raise EXCP_HLT
649 + * would trigger an abort.
650 + */
651 + return;
652 +#else
653 + CPUState *cs = env_cpu(env);
654 + uint32_t excp;
655 + int target_el;
656 + ARMCPU *cpu;
657 + uint64_t cntval, timeout, offset, cntvct, nexttick;
658 + int64_t next_event;
659 +
660 + /*
661 + * As for WFE if the event register is already set we can consume
662 + * the event and return immediately.
663 + */
664 + if (qatomic_xchg(&env->event_register, false)) {
665 + return;
666 + }
667 +
668 + /*
669 + * Don't bother to go into our "low power state" if
670 + * we would just wake up immediately.
671 + *
672 + * We want the value that we would get if we read CNTVCT_EL0 from
673 + * the current exception level, so the direct_access offset, not
674 + * the indirect_access one. Compare the pseudocode LocalTimeoutEvent(),
675 + * which calls VirtualCounterTimer().
676 + */
677 + cntval = gt_get_countervalue(env);
678 + offset = gt_direct_access_timer_offset(env, GTIMER_VIRT);
679 + cntvct = cntval - offset;
680 + timeout = env->xregs[rd];
681 + if (cpu_has_work(cs) || cntvct >= timeout) {
682 + return;
683 + }
684 +
685 + /* We might sleep, so now we check to see if we should trap */
686 + target_el = check_wfx_trap(env, true, &excp);
687 + if (target_el) {
688 + env->pc -= 4;
689 + raise_exception(env, excp, syn_wfx(1, 0xe, rd, true, WFET, false), target_el);
690 + }
691 +
692 + /*
693 + * If the CPU has entered the exclusive region we could sleep
694 + * until the global monitor moves from Exclusive to Open Access.
695 + * However it would be expensive for QEMU to fully model the
696 + * global monitor and not doing so would potentially trigger
697 + * deadlocks in WFE enabled locking code. However as WFE is a hint
698 + * instruction the architecture allows for the PE to leave
699 + * low-power state for any reason. QEMU chooses to treat being in
700 + * an exclusive region as such and return directly.
701 + */
702 + if (env->exclusive_addr != -1) {
703 + return;
704 + }
705 +
706 + /*
707 + * Finally work out if the timeout or event stream will kick in
708 + * earlier.
709 + *
710 + * The WFET should time out when CNTVCT_EL0 >= the specified value.
711 + */
712 + cpu = env_archcpu(env);
713 + if (uadd64_overflow(timeout, offset, &nexttick)) {
714 + nexttick = UINT64_MAX;
715 + }
716 + if (nexttick > INT64_MAX / gt_cntfrq_period_ns(cpu)) {
717 + nexttick = INT64_MAX;
718 + }
719 +
720 + next_event = gt_calc_next_event_stream(env);
721 + if (next_event > 0 && next_event < nexttick) {
722 + timer_mod(cpu->wfxt_timer, next_event);
723 + } else {
724 + if (nexttick == INT64_MAX) {
725 + timer_mod_ns(cpu->wfxt_timer, INT64_MAX);
726 + } else {
727 + timer_mod(cpu->wfxt_timer, nexttick);
728 + }
729 + }
730 +
731 + env->halt_reason = HALT_WFE;
732 + cs->exception_index = EXCP_HLT;
733 + cs->halted = 1;
734 + cpu_loop_exit(cs);
735 +#endif
736 +}
737 +
738 void HELPER(yield)(CPUARMState *env)
739 {
740 CPUState *cs = env_cpu(env);
target/arm/tcg/translate-a64.c
+8 -7
@@ -2195,14 +2195,15 @@ static bool trans_WFET(DisasContext *s, arg_WFET *a)
2195 return false;
2196 }
2197
2198 - /*
2199 - * We rely here on our WFE implementation being a NOP, so we
2200 - * don't need to do anything different to handle the WFET timeout
2201 - * from what trans_WFE does.
2202 - */
2203 - if (!(tb_cflags(s->base.tb) & CF_PARALLEL)) {
2204 - s->base.is_jmp = DISAS_WFE;
2198 + if (s->ss_active) {
2199 + /* Act like a NOP under architectural singlestep */
2200 + return true;
2201 }
2202 +
2203 + gen_a64_update_pc(s, 4);
2204 + gen_helper_wfet(tcg_env, tcg_constant_i32(a->rd));
2205 + /* Go back to the main loop to check for interrupts */
2206 + s->base.is_jmp = DISAS_EXIT;
2207 return true;
2208 }
2209