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
