@samitouri / QOSamiQemu / commits / 01b223f895

target/arm: enable WFE sleeping for A-profile

To enable full architectural behaviour for A-profile we need to do a number of things: - add support for the event stream to wake things up - add support for potential trap on sleep - handle the global monitor's interactions with WFE - remove the M-profile specific gates Event stream ------------ Two generic timers (K and H) are capable of generating timer event stream events. Provide a helper to calculate when the nearest one will happen. Now we can calculate when the next event stream event is we can re-use the wfxt_timer and configure it to fire as we enter a WFE that is going to sleep. Reverse the M-profile logic so we can enter a sleep state in both profiles. We also take care to use atomics for accessing env->event_register as we now have potential access outside the vCPU context. Traps ----- A-profile can trap WFE's *if* the instruction would otherwise sleep. To do this we need to pass the instruction size so we can deal with the is_16bit syndrome encoding. Global Monitor -------------- 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-7-alex.bennee@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org>

Alex Bennée committed Jun 24, 2026 at 11:30 UTC 01b223f895a72106947fae39d39c0cc15d952ba4
5 files changed +156 -45
target/arm/cpu.c
+13
@@ -887,10 +887,23 @@ bool arm_cpu_exec_halt(CPUState *cs)
887 }
888 #endif
889
890 +/*
891 + * Unlike almost everything else that messes with the halt_reason and
892 + * event_register details the timer callbacks are not in the vCPU
893 + * context.
894 + *
895 + * To prevent races we atomically consume a HALT_WFE and set the event
896 + * register. Either way we trigger the an exit event.
897 + */
898 static void arm_wfxt_timer_cb(void *opaque)
899 {
900 ARMCPU *cpu = opaque;
901 CPUState *cs = CPU(cpu);
902 + CPUARMState *env = &cpu->env;
903 +
904 + if (qatomic_cmpxchg(&env->halt_reason, HALT_WFE, NOT_HALTED)) {
905 + qatomic_set(&env->event_register, true);
906 + }
907
908 /*
909 * We expect the CPU to be halted; this will cause arm_cpu_is_work()
target/arm/tcg/helper-defs.h
+1 -1
@@ -54,7 +54,7 @@ DEF_HELPER_2(exception_swstep, noreturn, env, i32)
54 DEF_HELPER_2(exception_pc_alignment, noreturn, env, vaddr)
55 DEF_HELPER_1(setend, void, env)
56 DEF_HELPER_2(wfi, void, env, i32)
57 -DEF_HELPER_1(wfe, void, env)
57 +DEF_HELPER_2(wfe, void, env, i32)
58 DEF_HELPER_2(wfit, void, env, i32)
59 DEF_HELPER_1(yield, void, env)
60 DEF_HELPER_1(pre_hvc, void, env)
target/arm/tcg/op_helper.c
+136 -20
@@ -484,7 +484,98 @@ void HELPER(sev)(CPUARMState *env)
484 }
485 }
486
487 -void HELPER(wfe)(CPUARMState *env)
487 +#ifndef CONFIG_USER_ONLY
488 +/*
489 + * Event Stream events don't do anything apart from wake up sleeping
490 + * cores. These helpers calculate the next event stream event time so
491 + * the WFE helper can decide when its next wake up tick will be.
492 + */
493 +static int64_t gt_recalc_one_evt(CPUARMState *env, uint32_t control, uint64_t offset)
494 +{
495 + ARMCPU *cpu = env_archcpu(env);
496 + bool evnten = FIELD_EX32(control, CNTxCTL, EVNTEN);
497 +
498 + if (evnten) {
499 + int evnti = FIELD_EX32(control, CNTxCTL, EVNTI);
500 + bool evntis = FIELD_EX32(control, CNTxCTL, EVNTIS);
501 + bool evntdir = FIELD_EX32(control, CNTxCTL, EVNTDIR);
502 + /*
503 + * To figure out when the next event timer should fire we need
504 + * to calculate which bit of the counter we want to flip and
505 + * which transition counts.
506 + *
507 + * So we calculate 1 << bit - current lower bits and then add
508 + * 1 << bit if the bit needs to flip twice to meet evntdir
509 + */
510 + int bit = evntis ? evnti + 8 : evnti;
511 + uint64_t count = gt_get_countervalue(env) - offset;
512 + uint64_t target_bit = BIT_ULL(bit);
513 + uint64_t lower_bits = MAKE_64BIT_MASK(0, bit - 1);
514 + uint64_t next_tick = target_bit - (count & lower_bits);
515 + uint64_t abstick;
516 +
517 + /* do we need to bit flip twice? */
518 + if (((count & target_bit) != 0) ^ evntdir) {
519 + next_tick += target_bit;
520 + }
521 +
522 + /*
523 + * Note that the desired next expiry time might be beyond the
524 + * signed-64-bit range of a QEMUTimer -- in this case we just
525 + * set the timer for as far in the future as possible. When the
526 + * timer expires we will reset the timer for any remaining period.
527 + */
528 + if (uadd64_overflow(next_tick, offset, &abstick)) {
529 + abstick = UINT64_MAX;
530 + }
531 + if (abstick > INT64_MAX / gt_cntfrq_period_ns(cpu)) {
532 + return INT64_MAX;
533 + } else {
534 + return abstick;
535 + }
536 + }
537 +
538 + return -1;
539 +}
540 +
541 +/*
542 + * Calculate the next event stream time and return it. Returns -1 if
543 + * no event streams are enabled. It is up to the WFE helpers to decide
544 + * on the next time.
545 + */
546 +static int64_t gt_calc_next_event_stream(CPUARMState *env)
547 +{
548 + ARMCPU *cpu = env_archcpu(env);
549 + uint64_t hcr = arm_hcr_el2_eff(env);
550 + int64_t next_time = -1;
551 + uint64_t offset;
552 +
553 + /* Unless we are missing EL2 this can generate events */
554 + if (arm_feature(env, ARM_FEATURE_EL2)) {
555 + offset = gt_direct_access_timer_offset(env, GTIMER_PHYS);
556 + next_time = gt_recalc_one_evt(env, env->cp15.cnthctl_el2, offset);
557 + }
558 +
559 + /* Event stream events from virtual counter enabled? */
560 + if (!cpu_isar_feature(aa64_vh, cpu) ||
561 + !((hcr & (HCR_E2H | HCR_TGE)) == (HCR_E2H | HCR_TGE))) {
562 + int64_t next_virt_time;
563 + offset = gt_direct_access_timer_offset(env, GTIMER_VIRT);
564 + next_virt_time = gt_recalc_one_evt(env, env->cp15.c14_cntkctl, offset);
565 +
566 + /* is this earlier than the next physical event? */
567 + if (next_virt_time > 0) {
568 + if (next_time < 0 || next_virt_time < next_time) {
569 + next_time = next_virt_time;
570 + }
571 + }
572 + }
573 +
574 + return next_time;
575 +}
576 +#endif
577 +
578 +void HELPER(wfe)(CPUARMState *env, uint32_t insn_len)
579 {
580 #ifdef CONFIG_USER_ONLY
581 /*
@@ -496,32 +587,57 @@ void HELPER(wfe)(CPUARMState *env)
587 #else
588 /*
589 * WFE (Wait For Event) is a hint instruction.
499 - * For Cortex-M (M-profile), we implement the strict architectural behavior:
590 + *
591 * 1. Check the Event Register (set by SEV or SEVONPEND).
592 * 2. If set, clear it and continue (consume the event).
593 */
503 - if (arm_feature(env, ARM_FEATURE_M)) {
504 - CPUState *cs = env_cpu(env);
594 + CPUState *cs = env_cpu(env);
595 + ARMCPU *cpu = env_archcpu(env);
596 + uint32_t excp;
597 + int target_el;
598
506 - if (env->event_register) {
507 - env->event_register = false;
508 - return;
599 + if (qatomic_xchg(&env->event_register, false)) {
600 + return;
601 + }
602 +
603 + /* We might sleep, so now we check to see if we should trap */
604 + target_el = check_wfx_trap(env, true, &excp);
605 + if (target_el) {
606 + if (env->aarch64) {
607 + env->pc -= insn_len;
608 + } else {
609 + env->regs[15] -= insn_len;
610 }
611 + raise_exception(env, excp, syn_wfx(1, 0xe, 0, false, WFE, insn_len == 2),
612 + target_el);
613 + }
614
511 - env->halt_reason = HALT_WFE;
512 - cs->exception_index = EXCP_HLT;
513 - cs->halted = 1;
514 - cpu_loop_exit(cs);
515 - } else {
516 - /*
517 - * For A-profile and others, we rely on the existing "yield" behavior.
518 - * Don't actually halt the CPU, just yield back to top
519 - * level loop. This is not going into a "low power state"
520 - * (ie halting until some event occurs), so we never take
521 - * a configurable trap to a different exception level
522 - */
523 - HELPER(yield)(env);
615 + /*
616 + * If the CPU has entered the exclusive region we could sleep
617 + * until the global monitor moves from Exclusive to Open Access.
618 + * However it would be expensive for QEMU to fully model the
619 + * global monitor and not doing so would potentially trigger
620 + * deadlocks in WFE enabled locking code. However as WFE is a hint
621 + * instruction the architecture allows for the PE to leave
622 + * low-power state for any reason. QEMU chooses to treat being in
623 + * an exclusive region as such and return directly.
624 + */
625 + if (env->exclusive_addr != -1) {
626 + return;
627 }
628 +
629 + /* For A-profile we also can be woken by the event stream */
630 + if (cpu->wfxt_timer) {
631 + int64_t next_event = gt_calc_next_event_stream(env);
632 + if (next_event > 0) {
633 + timer_mod(cpu->wfxt_timer, next_event);
634 + }
635 + }
636 +
637 + env->halt_reason = HALT_WFE;
638 + cs->exception_index = EXCP_HLT;
639 + cs->halted = 1;
640 + cpu_loop_exit(cs);
641 #endif
642 }
643
target/arm/tcg/translate-a64.c
+2 -10
@@ -2161,15 +2161,7 @@ static bool trans_SEVL(DisasContext *s, arg_SEV *a)
2161
2162 static bool trans_WFE(DisasContext *s, arg_WFI *a)
2163 {
2164 - /*
2165 - * When running in MTTCG we don't generate jumps to the yield and
2166 - * WFE helpers as it won't affect the scheduling of other vCPUs.
2167 - * If we wanted to more completely model WFE/SEV so we don't busy
2168 - * spin unnecessarily we would need to do something more involved.
2169 - */
2170 - if (!(tb_cflags(s->base.tb) & CF_PARALLEL)) {
2171 - s->base.is_jmp = DISAS_WFE;
2172 - }
2164 + s->base.is_jmp = DISAS_WFE;
2165 return true;
2166 }
2167
@@ -11275,7 +11267,7 @@ static void aarch64_tr_tb_stop(DisasContextBase *dcbase, CPUState *cpu)
11267 */
11268 case DISAS_WFE:
11269 gen_a64_update_pc(dc, 4);
11278 - gen_helper_wfe(tcg_env);
11270 + gen_helper_wfe(tcg_env, tcg_constant_i32(4));
11271 tcg_gen_exit_tb(NULL, 0);
11272 break;
11273 case DISAS_WFI:
target/arm/tcg/translate.c
+4 -14
@@ -3273,19 +3273,9 @@ static bool trans_SEVL(DisasContext *s, arg_SEV *a)
3273
3274 static bool trans_WFE(DisasContext *s, arg_WFE *a)
3275 {
3276 - /*
3277 - * When running single-threaded TCG code, use the helper to ensure that
3278 - * the next round-robin scheduled vCPU gets a crack.
3279 - *
3280 - * For Cortex-M, we implement the architectural WFE behavior (sleeping
3281 - * until an event occurs or the Event Register is set).
3282 - * For other profiles, we currently treat this as a NOP or yield,
3283 - * to preserve existing performance characteristics.
3284 - */
3285 - if (!(tb_cflags(s->base.tb) & CF_PARALLEL)) {
3286 - gen_update_pc(s, curr_insn_len(s));
3287 - s->base.is_jmp = DISAS_WFE;
3288 - }
3276 + /* For WFE, halt the vCPU until an event. */
3277 + gen_update_pc(s, curr_insn_len(s));
3278 + s->base.is_jmp = DISAS_WFE;
3279 return true;
3280 }
3281
@@ -6857,7 +6847,7 @@ static void arm_tr_tb_stop(DisasContextBase *dcbase, CPUState *cpu)
6847 tcg_gen_exit_tb(NULL, 0);
6848 break;
6849 case DISAS_WFE:
6860 - gen_helper_wfe(tcg_env);
6850 + gen_helper_wfe(tcg_env, tcg_constant_i32(curr_insn_len(dc)));
6851 /*
6852 * The helper can return if the event register is set, so we
6853 * must go back to the main loop to check for events.