On 8/18/2026 6:31 PM, Brian Cain wrote:
> In system mode, route accesses to the qtimer-backed global
> TIMERLO/TIMERHI so guest reads see a live, monotonically increasing
> timer.
>
> In user mode we derive it from QEMU_CLOCK_VIRTUAL.
>
> Signed-off-by: Brian Cain <[email protected]>
> ---
> target/hexagon/helper.h | 4 ++++
> target/hexagon/genptr.c | 29 +++++++++++++++++++++++++++++
> target/hexagon/op_helper.c | 21 +++++++++++++++++++++
> tests/tcg/hexagon/reg_mut.c | 11 ++++++++---
> 4 files changed, 62 insertions(+), 3 deletions(-)
>
> diff --git a/target/hexagon/helper.h b/target/hexagon/helper.h
> index 78dc28ca9e5..e39afd623b5 100644
> --- a/target/hexagon/helper.h
> +++ b/target/hexagon/helper.h
> @@ -113,6 +113,10 @@ DEF_HELPER_FLAGS_4(gvec_sabsdiff_w, TCG_CALL_NO_RWG,
> void, ptr, ptr, ptr, i32)
> DEF_HELPER_FLAGS_4(gvec_uabsdiff_b, TCG_CALL_NO_RWG, void, ptr, ptr, ptr,
> i32)
> DEF_HELPER_FLAGS_4(gvec_uabsdiff_h, TCG_CALL_NO_RWG, void, ptr, ptr, ptr,
> i32)
>
> +#if defined(CONFIG_USER_ONLY)
> +DEF_HELPER_FLAGS_0(utimer, TCG_CALL_NO_RWG, i64)
> +#endif
> +
> #if !defined(CONFIG_USER_ONLY)
> DEF_HELPER_3(raise_stack_overflow, void, env, i32, i32)
> DEF_HELPER_2(swi, void, env, i32)
> diff --git a/target/hexagon/genptr.c b/target/hexagon/genptr.c
> index 2a98b13b714..282a697d190 100644
> --- a/target/hexagon/genptr.c
> +++ b/target/hexagon/genptr.c
> @@ -411,6 +411,23 @@ static inline void gen_read_ctrl_reg(DisasContext *ctx,
> const int reg_num,
> } else if (reg_num == HEX_REG_QEMU_HVX_CNT) {
> tcg_gen_addi_tl(dest, hex_gpr[HEX_REG_QEMU_HVX_CNT],
> ctx->num_hvx_insns);
> +#ifndef CONFIG_USER_ONLY
> + } else if (reg_num == HEX_REG_UTIMERLO) {
> + gen_helper_sreg_read(dest, tcg_env,
> + tcg_constant_i32(HEX_SREG_TIMERLO));
> + } else if (reg_num == HEX_REG_UTIMERHI) {
> + gen_helper_sreg_read(dest, tcg_env,
> + tcg_constant_i32(HEX_SREG_TIMERHI));
> +#else
> + } else if (reg_num == HEX_REG_UTIMERLO) {
> + TCGv_i64 utimer = tcg_temp_new_i64();
> + gen_helper_utimer(utimer);
> + tcg_gen_extrl_i64_i32(dest, utimer);
> + } else if (reg_num == HEX_REG_UTIMERHI) {
> + TCGv_i64 utimer = tcg_temp_new_i64();
> + gen_helper_utimer(utimer);
> + tcg_gen_extrh_i64_i32(dest, utimer);
> +#endif
> } else {
> tcg_gen_mov_tl(dest, hex_gpr[reg_num]);
> }
> @@ -439,6 +456,18 @@ static inline void gen_read_ctrl_reg_pair(DisasContext
> *ctx, const int reg_num,
> tcg_gen_addi_tl(hvx_cnt, hex_gpr[HEX_REG_QEMU_HVX_CNT],
> ctx->num_hvx_insns);
> tcg_gen_concat_i32_i64(dest, hvx_cnt, hex_gpr[reg_num + 1]);
> +#ifndef CONFIG_USER_ONLY
> + } else if (reg_num == HEX_REG_UTIMERLO) {
> + TCGv lo = tcg_temp_new();
> + TCGv hi = tcg_temp_new();
> + gen_helper_sreg_read(lo, tcg_env,
> tcg_constant_i32(HEX_SREG_TIMERLO));
> + gen_helper_sreg_read(hi, tcg_env,
> tcg_constant_i32(HEX_SREG_TIMERHI));
> + tcg_gen_concat_i32_i64(dest, lo, hi);
> +#else
> + } else if (reg_num == HEX_REG_UTIMERLO) {
> + /* One helper call, so the pair is a coherent 64-bit snapshot. */
> + gen_helper_utimer(dest);
> +#endif
> } else {
> tcg_gen_concat_i32_i64(dest,
> hex_gpr[reg_num],
Out of the scope of this patch, but seems like we could use a switch
instead of if/else ladder.
> diff --git a/target/hexagon/op_helper.c b/target/hexagon/op_helper.c
> index 2cea1927263..df31ed2488a 100644
> --- a/target/hexagon/op_helper.c
> +++ b/target/hexagon/op_helper.c
> @@ -37,6 +37,9 @@
> #include "cpu_helper.h"
> #include "tcg/tcg-gvec-desc.h"
> #include "translate.h"
> +#ifdef CONFIG_USER_ONLY
> +#include "qemu/timer.h"
> +#endif
> #ifndef CONFIG_USER_ONLY
> #include "hw/hexagon/hexagon_globalreg.h"
> #include "hex_mmu.h"
> @@ -46,6 +49,24 @@
> #include "hexswi.h"
> #endif
>
> +#ifdef CONFIG_USER_ONLY
> +/*
> + * User mode has no qtimer device backing TIMERLO/TIMERHI, so derive the
> + * user timer directly from the virtual clock -- the same clock the qtimer
> + * counts -- at the qtimer's default 19.2MHz tick rate, masked to the
> + * qtimer's counter width.
> + */
> +#define HEX_UTIMER_FREQ_HZ 19200000ULL
> +#define HEX_UTIMER_CNT_MASK 0x00ffffffffffffffULL
> +
> +uint64_t HELPER(utimer)(void)
> +{
> + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
> + HEX_UTIMER_FREQ_HZ, NANOSECONDS_PER_SECOND) &
> + HEX_UTIMER_CNT_MASK;
> +}
> +#endif
> +
> #define SF_BIAS 127
> #define SF_MANTBITS 23
>
> diff --git a/tests/tcg/hexagon/reg_mut.c b/tests/tcg/hexagon/reg_mut.c
> index c5a39e55100..9ce18f3ebe5 100644
> --- a/tests/tcg/hexagon/reg_mut.c
> +++ b/tests/tcg/hexagon/reg_mut.c
> @@ -77,10 +77,10 @@ static inline void write_control_registers(void)
> check32(result, 0x00000000);
>
> WRITE_REG_NOCLOBBER(result, "utimerlo", 0xffffffff);
> - check32(result, 0x00000000);
> + check32_ne(result, 0xffffffff);
>
> WRITE_REG_NOCLOBBER(result, "utimerhi", 0xffffffff);
> - check32(result, 0x00000000);
> + check32_ne(result, 0xffffffff);
>
> /*
> * PC is special. Setting it to these values
> @@ -106,8 +106,13 @@ static inline void write_control_register_pairs(void)
> WRITE_REG_NOCLOBBER(result, "c15:14", 0xffffffffffffffff);
> check64(result, 0x0000000000000000);
>
> + /*
> + * c31:30 is UTIMERHI:UTIMERLO, a read-only free-running counter. The
> + * write must be discarded; the read-back is whatever the timer says,
> + * so only check that the written value did not stick.
> + */
> WRITE_REG_NOCLOBBER(result, "c31:30", 0xffffffffffffffff);
> - check64(result, 0x0000000000000000);
> + check64_ne(result, 0xffffffffffffffff);
>
> WRITE_REG_PAIR_ENCODED(result, "c9:8", (uint64_t) 0x0000000000000000,
> C9_8_EQ_R1_0);
Reviewed-by: Pierrick Bouvier <[email protected]>