From: Zephyr Li <[email protected]>
With icount enabled, helper_raise_exception() leaves ECALL in
icount_get_raw() because it exits without restoring the current TB
state. This makes minstret count an instruction that does not retire.
Adjust only the fixed minstret baseline so that mcycle accounting
remains unchanged.
Add an RV64 softmmu regression test for the issue.
Fixes: 4fe8ae09062d ("target/riscv: Combine mhpmcounter and mhpmcounterh")
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/4087
Signed-off-by: Zephyr Li <[email protected]>
Reviewed-by: Daniel Henrique Barboza <[email protected]>
Reviewed-by: Alistair Francis <[email protected]>
Message-ID: <[email protected]>
Signed-off-by: Alistair Francis <[email protected]>
(cherry picked from commit 5a9fa8b5820998a5aca02ecf3df12819f7ff2173)
(Mjt: trivial context fixup in #include statement)
Signed-off-by: Michael Tokarev <[email protected]>
diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c
index b5693663699..43251179efc 100644
--- a/target/riscv/op_helper.c
+++ b/target/riscv/op_helper.c
@@ -20,6 +20,9 @@
#include "qemu/osdep.h"
#include "cpu.h"
+#ifndef CONFIG_USER_ONLY
+#include "pmu.h"
+#endif
#include "internals.h"
#include "exec/cputlb.h"
#include "accel/tcg/cpu-ldst.h"
@@ -59,6 +62,9 @@ G_NORETURN void riscv_raise_exception(CPURISCVState *env,
void helper_raise_exception(CPURISCVState *env, uint32_t exception)
{
+#ifndef CONFIG_USER_ONLY
+ riscv_pmu_decr_instret(env);
+#endif
riscv_raise_exception(env, exception, 0);
}
diff --git a/target/riscv/pmu.c b/target/riscv/pmu.c
index 708f2ec7aaa..137fc5c39e2 100644
--- a/target/riscv/pmu.c
+++ b/target/riscv/pmu.c
@@ -101,6 +101,21 @@ static bool riscv_pmu_counter_enabled(RISCVCPU *cpu,
uint32_t ctr_idx)
}
}
+static bool riscv_pmu_counter_filtered(CPURISCVState *env, uint64_t cfg)
+{
+ bool virt_on = env->virt_enabled;
+
+ return (env->priv == PRV_M && (cfg & MHPMEVENT_BIT_MINH)) ||
+ (env->priv == PRV_S && virt_on &&
+ (cfg & MHPMEVENT_BIT_VSINH)) ||
+ (env->priv == PRV_U && virt_on &&
+ (cfg & MHPMEVENT_BIT_VUINH)) ||
+ (env->priv == PRV_S && !virt_on &&
+ (cfg & MHPMEVENT_BIT_SINH)) ||
+ (env->priv == PRV_U && !virt_on &&
+ (cfg & MHPMEVENT_BIT_UINH));
+}
+
/*
* Information needed to update counters:
* new_priv, new_virt: To correctly save starting snapshot for the newly
@@ -196,12 +211,27 @@ void riscv_pmu_update_fixed_ctrs(CPURISCVState *env,
target_ulong newpriv,
riscv_pmu_icount_update_priv(env, newpriv, new_virt);
}
+void riscv_pmu_decr_instret(CPURISCVState *env)
+{
+ if (!icount_enabled() ||
+ (env->mcountinhibit & COUNTEREN_IR) ||
+ riscv_pmu_counter_filtered(env, env->minstretcfg)) {
+ return;
+ }
+
+ /*
+ * minstret is derived from icount, which includes the current
+ * instruction. Move the baseline forward to exclude an instruction
+ * that raises an exception and therefore does not retire.
+ */
+ env->pmu_ctrs[2].mhpmcounter_prev++;
+}
+
int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
{
uint32_t ctr_idx;
CPURISCVState *env = &cpu->env;
uint64_t max_val = UINT64_MAX;
- bool virt_on = env->virt_enabled;
PMUCTRState *counter;
gpointer value;
@@ -219,17 +249,7 @@ int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum
riscv_pmu_event_idx event_idx)
return -1;
}
- /* Privilege mode filtering */
- if ((env->priv == PRV_M &&
- (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_MINH)) ||
- (env->priv == PRV_S && virt_on &&
- (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_VSINH)) ||
- (env->priv == PRV_U && virt_on &&
- (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_VUINH)) ||
- (env->priv == PRV_S && !virt_on &&
- (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_SINH)) ||
- (env->priv == PRV_U && !virt_on &&
- (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_UINH))) {
+ if (riscv_pmu_counter_filtered(env, env->mhpmevent_val[ctr_idx])) {
return 0;
}
diff --git a/target/riscv/pmu.h b/target/riscv/pmu.h
index 3853d0e2629..a9a67869e65 100644
--- a/target/riscv/pmu.h
+++ b/target/riscv/pmu.h
@@ -36,6 +36,7 @@ int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value,
uint32_t ctr_idx);
void riscv_pmu_update_fixed_ctrs(CPURISCVState *env, target_ulong newpriv,
bool new_virt);
+void riscv_pmu_decr_instret(CPURISCVState *env);
RISCVException riscv_pmu_read_ctr(CPURISCVState *env, target_ulong *val,
bool upper_half, uint32_t ctr_idx);
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target
b/tests/tcg/riscv64/Makefile.softmmu-target
index eb1ce6504a0..a50aa3236f6 100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -24,6 +24,10 @@ EXTRA_RUNS += run-test-mepc-masking
run-test-mepc-masking: test-mepc-masking
$(call run-test, $<, $(QEMU) $(QEMU_OPTS)$<)
+EXTRA_RUNS += run-test-minstret-ecall
+run-test-minstret-ecall: test-minstret-ecall
+ $(call run-test, $<, $(QEMU) -icount shift=1 $(QEMU_OPTS)$<)
+
EXTRA_RUNS += run-plugin-doubletrap
run-plugin-doubletrap: doubletrap
$(call run-test, $<, \
diff --git a/tests/tcg/riscv64/test-minstret-ecall.S
b/tests/tcg/riscv64/test-minstret-ecall.S
new file mode 100644
index 00000000000..ab268f7f229
--- /dev/null
+++ b/tests/tcg/riscv64/test-minstret-ecall.S
@@ -0,0 +1,55 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+ .option norvc
+
+ .text
+ .global _start
+_start:
+ lla t0, trap
+ csrw mtvec, t0
+
+ /*
+ * The first CSR read retires after obtaining s0. The ecall does not
+ * retire, so the trap handler must observe a difference of one.
+ */
+ csrr s0, minstret
+ ecall
+ sub t0, s1, s0
+ li t1, 1
+ bne t0, t1, fail
+
+ li a0, 0
+ j _exit
+
+trap:
+ csrr s1, minstret
+ csrr t0, mcause
+ li t1, 11 /* Environment call from M-mode */
+ bne t0, t1, fail
+
+ csrr t0, mepc
+ addi t0, t0, 4
+ csrw mepc, t0
+ mret
+
+fail:
+ li a0, 1
+
+_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
--
2.47.3