This commit extends support for WFI, WFE, WFIT, and WFET instructions
for A-profile ARM CPUs, ensuring proper architectural semantics and
full ISS (Instruction Specific Syndrome) field support for traps.

Key changes:
- Update `syn_wfx` in `target/arm/syndrome.h` to include `RN` (register
  number) and `RV` (register valid) fields using `registerfields.h`
  macros.
- Refactor `HELPER(wfi)` and `HELPER(wfit)` to use correct AArch64
  syndrome values (CV=0, COND=0xf).
- Implement `HELPER(wfet)` and update `trans_WFET` to support the new
  Wait For Event with Timeout instruction.
- Update `HELPER(wfe)` to implement proper A-profile semantics,
  including trap checks and event register handling.
- Extend `event_register` handling to all ARM CPUs (not just M-profile)
  by updating `HELPER(sev)` and `arm_cpu_has_work`.
- Declare WFxT helpers as `TCG_CALL_NO_WG` as they can raise
  exceptions.

đŸ¤– Generated with [eca](https://eca.dev)

Co-Authored-By: eca <[email protected]>

---
ajb
  - this commit is a bit big, it didn't follow the instructions to
  keep the commits small
  - I had to add DEF_HELPER_FLAGS_3 to the helpers as it didn't
  realise we need to ensure rd is potentially rectified.
---
 target/arm/syndrome.h          |  24 ++++++--
 target/arm/tcg/helper-defs.h   |   3 +-
 target/arm/cpu.c               |   6 +-
 target/arm/tcg/op_helper.c     | 101 +++++++++++++++++++++++----------
 target/arm/tcg/translate-a64.c |  17 +++---
 5 files changed, 104 insertions(+), 47 deletions(-)

diff --git a/target/arm/syndrome.h b/target/arm/syndrome.h
index bff61f052cc..49861758262 100644
--- a/target/arm/syndrome.h
+++ b/target/arm/syndrome.h
@@ -26,6 +26,7 @@
 #define TARGET_ARM_SYNDROME_H
 
 #include "qemu/bitops.h"
+#include "hw/core/registerfields.h"
 
 /* Valid Syndrome Register EC field values */
 enum arm_exception_class {
@@ -352,11 +353,26 @@ static inline uint32_t syn_breakpoint(int same_el)
         | ARM_EL_IL | 0x22;
 }
 
-static inline uint32_t syn_wfx(int cv, int cond, int ti, bool is_16bit)
+FIELD(WFX_ISS, TI, 0, 2)
+FIELD(WFX_ISS, RV, 14, 1)
+FIELD(WFX_ISS, RN, 15, 5)
+FIELD(WFX_ISS, COND, 20, 4)
+FIELD(WFX_ISS, CV, 24, 1)
+
+static inline uint32_t syn_wfx(int cv, int cond, int rd, int rv, int ti, bool 
is_16bit)
 {
-    return (EC_WFX_TRAP << ARM_EL_EC_SHIFT) |
-           (is_16bit ? 0 : (1 << ARM_EL_IL_SHIFT)) |
-           (cv << 24) | (cond << 20) | ti;
+    uint32_t res = (EC_WFX_TRAP << ARM_EL_EC_SHIFT);
+
+    res = FIELD_DP32(res, WFX_ISS, CV, cv);
+    res = FIELD_DP32(res, WFX_ISS, COND, cond);
+    res = FIELD_DP32(res, WFX_ISS, RN, rd);
+    res = FIELD_DP32(res, WFX_ISS, RV, rv);
+    res = FIELD_DP32(res, WFX_ISS, TI, ti);
+
+    if (!is_16bit) {
+        res |= ARM_EL_IL;
+    }
+    return res;
 }
 
 static inline uint32_t syn_illegalstate(void)
diff --git a/target/arm/tcg/helper-defs.h b/target/arm/tcg/helper-defs.h
index 5a10a9fba3b..d54eb63eef6 100644
--- a/target/arm/tcg/helper-defs.h
+++ b/target/arm/tcg/helper-defs.h
@@ -55,7 +55,8 @@ DEF_HELPER_2(exception_pc_alignment, noreturn, env, vaddr)
 DEF_HELPER_1(setend, void, env)
 DEF_HELPER_2(wfi, void, env, i32)
 DEF_HELPER_1(wfe, void, env)
-DEF_HELPER_2(wfit, void, env, i64)
+DEF_HELPER_FLAGS_3(wfit, TCG_CALL_NO_WG, void, env, i64, i32)
+DEF_HELPER_FLAGS_3(wfet, TCG_CALL_NO_WG, void, env, i64, i32)
 DEF_HELPER_1(yield, void, env)
 DEF_HELPER_1(pre_hvc, void, env)
 DEF_HELPER_2(pre_smc, void, env, i32)
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 10f8280eef2..bc789515af9 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -144,10 +144,8 @@ static bool arm_cpu_has_work(CPUState *cs)
 {
     ARMCPU *cpu = ARM_CPU(cs);
 
-    if (arm_feature(&cpu->env, ARM_FEATURE_M)) {
-        if (cpu->env.event_register) {
-            return true;
-        }
+    if (cpu->env.event_register) {
+        return true;
     }
 
     return (cpu->power_state != PSCI_OFF)
diff --git a/target/arm/tcg/op_helper.c b/target/arm/tcg/op_helper.c
index aa14f15eb62..37538daea74 100644
--- a/target/arm/tcg/op_helper.c
+++ b/target/arm/tcg/op_helper.c
@@ -393,13 +393,17 @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len)
     }
 
     if (target_el) {
-        if (env->aarch64) {
+        int cv = 1, cond = 0xe;
+
+        if (is_a64(env)) {
             env->pc -= insn_len;
+            cv = 0;
+            cond = 0xf;
         } else {
             env->regs[15] -= insn_len;
         }
 
-        raise_exception(env, excp, syn_wfx(1, 0xe, 0, insn_len == 2),
+        raise_exception(env, excp, syn_wfx(cv, cond, 0, 0, 0, insn_len == 2),
                         target_el);
     }
 
@@ -409,7 +413,7 @@ void HELPER(wfi)(CPUARMState *env, uint32_t insn_len)
 #endif
 }
 
-void HELPER(wfit)(CPUARMState *env, uint64_t timeout)
+void HELPER(wfit)(CPUARMState *env, uint64_t timeout, uint32_t rd)
 {
 #ifdef CONFIG_USER_ONLY
     /*
@@ -448,7 +452,7 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout)
 
     if (target_el) {
         env->pc -= 4;
-        raise_exception(env, excp, syn_wfx(1, 0xe, 2, false), target_el);
+        raise_exception(env, excp, syn_wfx(0, 0xf, rd, 1, 2, false), 
target_el);
     }
 
     if (uadd64_overflow(timeout, offset, &nexttick)) {
@@ -469,14 +473,50 @@ void HELPER(wfit)(CPUARMState *env, uint64_t timeout)
 #endif
 }
 
+void HELPER(wfet)(CPUARMState *env, uint64_t timeout, uint32_t rd)
+{
+#ifdef CONFIG_USER_ONLY
+    return;
+#else
+    ARMCPU *cpu = env_archcpu(env);
+    CPUState *cs = env_cpu(env);
+    uint32_t excp;
+    int target_el = check_wfx_trap(env, true, &excp);
+    uint64_t cntval = gt_get_countervalue(env);
+    uint64_t offset = gt_direct_access_timer_offset(env, GTIMER_VIRT);
+    uint64_t cntvct = cntval - offset;
+    uint64_t nexttick;
+
+    if (env->event_register || cpu_has_work(cs) || cntvct >= timeout) {
+        env->event_register = false;
+        return;
+    }
+
+    if (target_el) {
+        env->pc -= 4;
+        raise_exception(env, excp, syn_wfx(0, 0xf, rd, 1, 3, false), 
target_el);
+    }
+
+    if (uadd64_overflow(timeout, offset, &nexttick)) {
+        nexttick = UINT64_MAX;
+    }
+    if (nexttick > INT64_MAX / gt_cntfrq_period_ns(cpu)) {
+        timer_mod_ns(cpu->wfxt_timer, INT64_MAX);
+    } else {
+        timer_mod(cpu->wfxt_timer, nexttick);
+    }
+    cs->exception_index = EXCP_HLT;
+    cs->halted = 1;
+    cpu_loop_exit(cs);
+#endif
+}
+
 void HELPER(sev)(CPUARMState *env)
 {
     CPUState *cs = env_cpu(env);
     CPU_FOREACH(cs) {
         ARMCPU *target_cpu = ARM_CPU(cs);
-        if (arm_feature(&target_cpu->env, ARM_FEATURE_M)) {
-            target_cpu->env.event_register = true;
-        }
+        target_cpu->env.event_register = true;
         if (!qemu_cpu_is_self(cs)) {
             qemu_cpu_kick(cs);
         }
@@ -493,33 +533,34 @@ void HELPER(wfe)(CPUARMState *env)
      */
     return;
 #else
-    /*
-     * WFE (Wait For Event) is a hint instruction.
-     * For Cortex-M (M-profile), we implement the strict architectural 
behavior:
-     * 1. Check the Event Register (set by SEV or SEVONPEND).
-     * 2. If set, clear it and continue (consume the event).
-     */
-    if (arm_feature(env, ARM_FEATURE_M)) {
-        CPUState *cs = env_cpu(env);
+    CPUState *cs = env_cpu(env);
+    uint32_t excp;
+    int target_el = check_wfx_trap(env, true, &excp);
 
-        if (env->event_register) {
-            env->event_register = false;
-            return;
+    if (env->event_register) {
+        env->event_register = false;
+        return;
+    }
+
+    if (target_el) {
+        bool is_16bit = false;
+        if (is_a64(env)) {
+            env->pc -= 4;
+        } else {
+            is_16bit = env->thumb;
+            env->regs[15] -= (is_16bit ? 2 : 4);
         }
 
-        cs->exception_index = EXCP_HLT;
-        cs->halted = 1;
-        cpu_loop_exit(cs);
-    } else {
-        /*
-         * For A-profile and others, we rely on the existing "yield" behavior.
-         * Don't actually halt the CPU, just yield back to top
-         * level loop. This is not going into a "low power state"
-         * (ie halting until some event occurs), so we never take
-         * a configurable trap to a different exception level
-         */
-        HELPER(yield)(env);
+        raise_exception(env, excp,
+                        syn_wfx(is_a64(env) ? 0 : 1,
+                                is_a64(env) ? 0xf : 0xe,
+                                0, 0, 1, is_16bit),
+                        target_el);
     }
+
+    cs->exception_index = EXCP_HLT;
+    cs->halted = 1;
+    cpu_loop_exit(cs);
 #endif
 }
 
diff --git a/target/arm/tcg/translate-a64.c b/target/arm/tcg/translate-a64.c
index 5d261a5e32b..f76a00d1329 100644
--- a/target/arm/tcg/translate-a64.c
+++ b/target/arm/tcg/translate-a64.c
@@ -2064,7 +2064,7 @@ static bool trans_WFIT(DisasContext *s, arg_WFIT *a)
     }
 
     gen_a64_update_pc(s, 4);
-    gen_helper_wfit(tcg_env, cpu_reg(s, a->rd));
+    gen_helper_wfit(tcg_env, cpu_reg(s, a->rd), tcg_constant_i32(a->rd));
     /* Go back to the main loop to check for interrupts */
     s->base.is_jmp = DISAS_EXIT;
     return true;
@@ -2076,14 +2076,15 @@ static bool trans_WFET(DisasContext *s, arg_WFET *a)
         return false;
     }
 
-    /*
-     * We rely here on our WFE implementation being a NOP, so we
-     * don't need to do anything different to handle the WFET timeout
-     * from what trans_WFE does.
-     */
-    if (!(tb_cflags(s->base.tb) & CF_PARALLEL)) {
-        s->base.is_jmp = DISAS_WFE;
+    if (s->ss_active) {
+        /* Act like a NOP under architectural singlestep */
+        return true;
     }
+
+    gen_a64_update_pc(s, 4);
+    gen_helper_wfet(tcg_env, cpu_reg(s, a->rd), tcg_constant_i32(a->rd));
+    /* Go back to the main loop to check for interrupts */
+    s->base.is_jmp = DISAS_EXIT;
     return true;
 }
 
-- 
2.47.3


Reply via email to