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/Makefile.softmmu-target | 4 ++
tests/tcg/riscv64/sscofpmf-cycle-overflow.S | 58 +++++++++++++++++++++++++++++
7 files changed, 100 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/Makefile.softmmu-target
b/tests/tcg/riscv64/Makefile.softmmu-target
index
97978c6707247bb786f41549ada69e12aab619ce..677244e8b187435b09fc6b7616e269992543587c
100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -44,6 +44,10 @@ TESTS += sscofpmf-overflow
run-sscofpmf-overflow: sscofpmf-overflow
$(call run-test, $<, $(QEMU) -cpu max -icount shift=0 $(QEMU_OPTS)$<)
+TESTS += sscofpmf-cycle-overflow
+run-sscofpmf-cycle-overflow: sscofpmf-cycle-overflow
+ $(call run-test, $<, $(QEMU) -cpu max -icount shift=3 $(QEMU_OPTS)$<)
+
EXTRA_RUNS += run-plugin-doubletrap
run-plugin-doubletrap: doubletrap
$(call run-test, $<, \
diff --git a/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
new file mode 100644
index
0000000000000000000000000000000000000000..1acf61ee54ab9b8dc6e5228a6bce3c013fe7201c
--- /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 0x323, zero /* mhpmevent3 */
+ li t0, -4096
+ csrw 0xb03, t0 /* mhpmcounter3 */
+ li t0, 1
+ csrw 0x323, t0 /* mhpmevent3: cycles */
+ csrr t1, mcycle
+
+1:
+ csrr t0, 0x323
+ 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