Raw host ticks keep advancing while the VM is stopped. Use
cpus_get_elapsed_ticks() for cycles and non-icount instruction counting,
and retain icount_get_raw() for instructions under icount. Document that
cpu_get_ticks() returns its stored value while VM ticks are disabled.

Under icount, cycles are already virtual nanoseconds. Convert only raw
instruction counts when scheduling overflow, avoiding a second scaling
of cycle distances. Add a cycle-overflow regression with icount shift=3.

Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-10/msg00668.html
Signed-off-by: TANG Tiancheng <[email protected]>
Reviewed-by: Daniel Henrique Barboza <[email protected]>
---
 system/cpu-timers.c                         |  4 +-
 system/cpus.c                               |  6 +--
 target/riscv/tcg/csr.c                      |  8 +---
 target/riscv/tcg/pmu.c                      | 52 +++++++++++++++-----------
 target/riscv/tcg/pmu.h                      |  1 +
 tests/tcg/riscv64/sscofpmf-cycle-overflow.S | 58 +++++++++++++++++++++++++++++
 tests/tcg/riscv64/system/meson.build        |  7 ++++
 7 files changed, 103 insertions(+), 33 deletions(-)

diff --git a/system/cpu-timers.c b/system/cpu-timers.c
index 
9919b46230f1caf8be1a1b6ef00acd94678437ce..0415636aff3f774f0de61fdac3969f5b45ae6993
 100644
--- a/system/cpu-timers.c
+++ b/system/cpu-timers.c
@@ -118,8 +118,8 @@ void cpu_enable_ticks(void)
 }
 
 /*
- * disable cpu_get_ticks() : the clock is stopped. You must not call
- * cpu_get_ticks() after that.
+ * Freeze VM ticks. While disabled, cpu_get_ticks() returns the stored tick
+ * value instead of sampling the advancing host counter.
  * Caller must hold BQL which serves as mutex for vm_clock_seqlock.
  */
 void cpu_disable_ticks(void)
diff --git a/system/cpus.c b/system/cpus.c
index 
e11a5aab6a696962d94ad30ac38acd8867b1bf88..f61639ae78277fd90cbddb0b9f75b134e3cc1a17
 100644
--- a/system/cpus.c
+++ b/system/cpus.c
@@ -237,9 +237,9 @@ void cpus_set_virtual_clock(int64_t new_time)
 }
 
 /*
- * return the time elapsed in VM between vm_start and vm_stop.  Unless
- * icount is active, cpus_get_elapsed_ticks() uses units of the host CPU cycle
- * counter.
+ * Return VM-elapsed ticks. While VM ticks are disabled, passage of host time
+ * does not advance the returned value. Unless icount is active, the units are
+ * those of the host CPU cycle counter.
  */
 int64_t cpus_get_elapsed_ticks(void)
 {
diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index 
d15a2d096cb6e13cd123ff9ae82ee7c643c2a961..60caee32dc0cf5b6a8492e0cf8ff15f71acc2087
 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1327,13 +1327,7 @@ static uint64_t 
riscv_pmu_ctr_get_fixed_counters_val(CPURISCVState *env,
     }
 
     if (!cfg_val) {
-        if (icount_enabled()) {
-                curr_val = inst ? icount_get_raw() : icount_get();
-        } else {
-            curr_val = cpu_get_host_ticks();
-        }
-
-        return curr_val;
+        return riscv_pmu_read_fixed_source(env, inst);
     }
 
     /* Update counter before reading. */
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 
f19f417e90e33a94d00007ef132ef4e154175b19..ea0ffe41258d4dbef9dc952655e6301c14ba1d23
 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -24,8 +24,14 @@
 #include "pmu.h"
 #include "exec/icount.h"
 #include "system/device_tree.h"
+#include "system/cpu-timers.h"
 
-#define RISCV_TIMEBASE_FREQ 1000000000 /* 1Ghz */
+/*
+ * cpu_get_ticks() does not expose the host tick frequency.  Use a 1 GHz
+ * approximation only when scheduling non-icount overflow checks; fixed
+ * counter values remain in host-tick units.
+ */
+#define RISCV_PMU_HOST_TICK_HZ_ASSUMED 1000000000
 
 static bool riscv_pmu_counter_valid(RISCVCPU *cpu, uint32_t ctr_idx)
 {
@@ -75,6 +81,19 @@ static bool riscv_pmu_counter_filtered(CPURISCVState *env, 
uint64_t cfg)
             (cfg & MHPMEVENT_BIT_UINH));
 }
 
+/*
+ * VM-elapsed ticks stop advancing while VM ticks are disabled.  Under
+ * icount, instruction events retain raw instruction-count units.
+ */
+uint64_t riscv_pmu_read_fixed_source(CPURISCVState *env, bool instret)
+{
+    if (instret && icount_enabled()) {
+        return icount_get_raw();
+    }
+
+    return cpus_get_elapsed_ticks();
+}
+
 /*
  * Information needed to update counters:
  *  new_priv, new_virt: To correctly save starting snapshot for the newly
@@ -96,11 +115,7 @@ static void riscv_pmu_icount_update_priv(CPURISCVState *env,
     uint64_t *counter_arr;
     uint64_t delta;
 
-    if (icount_enabled()) {
-        current_icount = icount_get_raw();
-    } else {
-        current_icount = cpu_get_host_ticks();
-    }
+    current_icount = riscv_pmu_read_fixed_source(env, true);
 
     if (env->virt_enabled) {
         g_assert(env->priv <= PRV_S);
@@ -137,11 +152,7 @@ static void riscv_pmu_cycle_update_priv(CPURISCVState *env,
     uint64_t *counter_arr;
     uint64_t delta;
 
-    if (icount_enabled()) {
-        current_ticks = icount_get();
-    } else {
-        current_ticks = cpu_get_host_ticks();
-    }
+    current_ticks = riscv_pmu_read_fixed_source(env, false);
 
     if (env->virt_enabled) {
         g_assert(env->priv <= PRV_S);
@@ -286,17 +297,15 @@ static bool riscv_pmu_event_supported(uint32_t event_idx)
     }
 }
 
-static int64_t pmu_icount_ticks_to_ns(int64_t value)
+static int64_t pmu_ticks_to_ns(CPURISCVState *env, uint32_t ctr_idx,
+                               int64_t value)
 {
-    int64_t ret = 0;
-
-    if (icount_enabled()) {
-        ret = icount_to_ns(value);
-    } else {
-        ret = (NANOSECONDS_PER_SECOND / RISCV_TIMEBASE_FREQ) * value;
+    if (icount_enabled() &&
+        riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
+        return icount_to_ns(value);
     }
 
-    return ret;
+    return (NANOSECONDS_PER_SECOND / RISCV_PMU_HOST_TICK_HZ_ASSUMED) * value;
 }
 
 void riscv_pmu_rebuild_event_map(CPURISCVState *env)
@@ -448,8 +457,9 @@ int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t 
value, uint32_t ctr_idx)
 
     if (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
         riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
-        overflow_ns = pmu_icount_ticks_to_ns((int64_t)overflow_delta);
-        overflow_left = pmu_icount_ticks_to_ns(overflow_left) ;
+        overflow_ns = pmu_ticks_to_ns(env, ctr_idx,
+                                      (int64_t)overflow_delta);
+        overflow_left = pmu_ticks_to_ns(env, ctr_idx, overflow_left);
     } else {
         return -1;
     }
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index 
910091690290cac9f77855f479bb9d90b2762efe..339a4b3ac09c4a91cddd9250824284203b16b4fa
 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -26,6 +26,7 @@ bool riscv_pmu_ctr_monitor_instructions(CPURISCVState *env,
                                         uint32_t target_ctr);
 bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env,
                                   uint32_t target_ctr);
+uint64_t riscv_pmu_read_fixed_source(CPURISCVState *env, bool instret);
 void riscv_pmu_timer_cb(void *priv);
 void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
 void riscv_pmu_rebuild_event_map(CPURISCVState *env);
diff --git a/tests/tcg/riscv64/sscofpmf-cycle-overflow.S 
b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
new file mode 100644
index 
0000000000000000000000000000000000000000..846d4651c4ee8f06df83bed85512ab9794552c28
--- /dev/null
+++ b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+       .option norvc
+       .option norelax
+
+       .text
+       .global _start
+_start:
+       /* UINT64_MAX - 4095 leaves 4096 cycle increments until overflow. */
+       csrw    mhpmevent3, zero
+       li      t0, -4096
+       csrw    mhpmcounter3, t0
+       li      t0, 1
+       csrw    mhpmevent3, t0          /* mhpmevent3: cycles */
+       csrr    t1, mcycle
+
+1:
+       csrr    t0, mhpmevent3
+       beqz    t0, fail
+       li      t2, 1
+       slli    t2, t2, 63
+       and     t0, t0, t2
+       bnez    t0, pass
+
+       /*
+        * Allow 16384 cycles for OF to become visible. With shift=3, scaling
+        * the 4096-cycle distance twice would delay it to about 32768 cycles.
+        */
+       csrr    t0, mcycle
+       sub     t0, t0, t1
+       li      t2, 16384
+       bltu    t0, t2, 1b
+
+fail:
+       li      a0, 1
+       j       exit
+
+pass:
+       li      a0, 0
+
+exit:
+       lla     a1, semiargs
+       li      t0, 0x20026             /* ADP_Stopped_ApplicationExit */
+       sd      t0, 0(a1)
+       sd      a0, 8(a1)
+       li      a0, 0x20                /* TARGET_SYS_EXIT_EXTENDED */
+
+       /* Semihosting call sequence. */
+       .balign 16
+       slli    zero, zero, 0x1f
+       ebreak
+       srai    zero, zero, 0x7
+       j       .
+
+       .data
+       .balign 16
+semiargs:
+       .space  16
diff --git a/tests/tcg/riscv64/system/meson.build 
b/tests/tcg/riscv64/system/meson.build
index 
ebe78200fd551b42d3c99ae19ca03803797f803d..668a9a76070b6f16087b08ea84683d883312e951
 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -68,6 +68,13 @@ tests += {
   },
 }
 
+tests += {
+  'sscofpmf-cycle-overflow.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', '-icount', 'shift=3', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv64' in emulators
   tcg_tests += {
     'riscv64-softmmu': {

-- 
2.43.0


Reply via email to