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 <noreply@eca.dev>
---
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
On Tue, 24 Feb 2026 at 12:10, Alex Bennée <alex.bennee@linaro.org> wrote: > > 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. Yeah, no. This all needs to be split out to be anything close to reviewable. In particular "report the right syndrome info", "make WFE/SEV not NOPs on A profile like they are on M-profile" and "do better with WFET" are all separate things. Also, this doesn't change any of the places that it needs to change to implement the places that architecturally set the event register (see R_XRZRK in the Arm ARM), so it will break code that uses WFE. Notably for A-profile this includes generic timer "event streams", which we currently don't implement at all. thanks -- PMM
Peter Maydell <peter.maydell@linaro.org> writes:
> On Tue, 24 Feb 2026 at 12:10, Alex Bennée <alex.bennee@linaro.org> wrote:
>>
>> 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.
>
> Yeah, no. This all needs to be split out to be anything
> close to reviewable. In particular "report the right
> syndrome info", "make WFE/SEV not NOPs on A profile like
> they are on M-profile" and "do better with WFET" are all
> separate things.
Yeah it was surprising it didn't follow its own plan which did have a
nice breakdown of patches. When I do my human spin I'll make sure it is
split up better.
> Also, this doesn't change any of the places that it needs to
> change to implement the places that architecturally set the
> event register (see R_XRZRK in the Arm ARM),
I was having trouble finding that but searching for just XRZRK finds the
subscript text:
The Event Register for a PE is set by any of the following:
- A Send Event instruction, SEV, executed by any PE in the system.
- A Send Event Local instruction, SEVL, executed by the PE
- An exception return.
These seem easy enough
- The clearing of the global monitor for the PE
Is this the LSTREX monitor?
- An event from a Generic Timer event stream, see Event streams.
So this looks like sub-timer events that trigger events but aren't timer
expiry events? I shall have a look to see if the kernel uses this.
- An event sent by some IMPLEMENTATION DEFINED mechanism.
I assume we don't need to care about IMPDEF cases for our current CPU models?
> so it will
> break code that uses WFE. Notably for A-profile this includes
> generic timer "event streams", which we currently don't implement
> at all.
>
> thanks
> -- PMM
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
On Wed, 25 Feb 2026 at 11:22, Alex Bennée <alex.bennee@linaro.org> wrote: > > Peter Maydell <peter.maydell@linaro.org> writes: > > Also, this doesn't change any of the places that it needs to > > change to implement the places that architecturally set the > > event register (see R_XRZRK in the Arm ARM), > > I was having trouble finding that but searching for just XRZRK finds the > subscript text: > > The Event Register for a PE is set by any of the following: > - A Send Event instruction, SEV, executed by any PE in the system. > - A Send Event Local instruction, SEVL, executed by the PE > - An exception return. > > These seem easy enough > > - The clearing of the global monitor for the PE > > Is this the LSTREX monitor? Yes (see Arm ARM section B2.12.2). Note that as "global" implies, this is cross-CPU, not just internal to this one. > - An event from a Generic Timer event stream, see Event streams. > > So this looks like sub-timer events that trigger events but aren't timer > expiry events? I shall have a look to see if the kernel uses this. It will both use it internally and also exposes it to userspace (in the sense of providing a hwcap that says "we generate an event stream so you can rely on WFE waking up after a while"). The kernel config symbol is ARM_ARCH_TIMER_EVTSTREAM. For internal use, see for instance arch/arm64/lib/delay.c : if the CPU has event streams but not WFIT/WFET then it will use the event stream to implement the delay. > - An event sent by some IMPLEMENTATION DEFINED mechanism. > > I assume we don't need to care about IMPDEF cases for our current CPU models? Yes, we don't need to worry about this part. -- PMM
© 2016 - 2026 Red Hat, Inc.