With icount, helper_raise_exception() excludes faulting instructions from
minstret but not HPM counters selecting HW_INSTRUCTIONS.
Adjust the baseline of every running instruction counter that counts the
current privilege mode. Do not read icount here: the helper can run inside
a TB. Keep the existing timer, since excluding an instruction can only
postpone overflow and expiry checks for an actual wrap.
Extend the ECALL regression to compare both counters and add an LPAD
fault test covering an exception inside a multi-instruction TB.
Fixes: 14664483457b ("target/riscv: Add sscofpmf extension support")
Signed-off-by: TANG Tiancheng <[email protected]>
---
target/riscv/tcg/pmu.c | 35 ++++++++++++----
tests/tcg/riscv64/Makefile.softmmu-target | 6 ++-
tests/tcg/riscv64/pmu-lpad.S | 69 +++++++++++++++++++++++++++++++
tests/tcg/riscv64/test-minstret-ecall.S | 26 ++++++++++++
4 files changed, 128 insertions(+), 8 deletions(-)
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index
f88f6ae671d877ed874d22c9d4b840edb6f433a5..08298010b6d06f5792fa14ff81fe2f7a28c6476f
100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -387,18 +387,39 @@ void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t
value)
void riscv_pmu_decr_instret(CPURISCVState *env)
{
- if (!icount_enabled() ||
- (env->mcountinhibit & COUNTEREN_IR) ||
- riscv_pmu_counter_filtered(env, env->minstretcfg)) {
+ RISCVCPU *cpu = env_archcpu(env);
+ uint32_t ctr_mask;
+
+ if (!icount_enabled()) {
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.
+ * Fixed instruction events are derived from icount, which includes the
+ * current instruction. Move the baseline of each running
+ * instruction-source counter that counts the current privilege mode to
+ * exclude an instruction that raises an exception and does not retire.
+ *
+ * Do not read icount here: this helper can run in the middle of a TB.
+ * Excluding an instruction only postpones overflow, so keep the current
+ * timer deadline. The expiry handler checks for an actual counter wrap.
*/
- env->pmu_ctrs[2].mhpmcounter_prev++;
+ ctr_mask = COUNTEREN_IR |
+ riscv_pmu_event_counter_mask(
+ cpu, RISCV_PMU_EVENT_HW_INSTRUCTIONS);
+ while (ctr_mask) {
+ uint32_t ctr_idx = ctz32(ctr_mask);
+ uint64_t cfg = ctr_idx == 2 ? env->minstretcfg :
+ env->mhpmevent_val[ctr_idx];
+
+ ctr_mask &= ~BIT(ctr_idx);
+ if (!riscv_pmu_fixed_ctr_running(env, ctr_idx) ||
+ riscv_pmu_counter_filtered(env, cfg)) {
+ continue;
+ }
+
+ env->pmu_ctrs[ctr_idx].mhpmcounter_prev++;
+ }
}
int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
diff --git a/tests/tcg/riscv64/Makefile.softmmu-target
b/tests/tcg/riscv64/Makefile.softmmu-target
index
b100f7a9ddf8e31fb104aa834b28aa64fc31f50e..deb44ece5feeb13756f9f3abcd38258d75bd9ba1
100644
--- a/tests/tcg/riscv64/Makefile.softmmu-target
+++ b/tests/tcg/riscv64/Makefile.softmmu-target
@@ -24,10 +24,14 @@ 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
+TESTS += test-minstret-ecall
run-test-minstret-ecall: test-minstret-ecall
$(call run-test, $<, $(QEMU) -icount shift=1 $(QEMU_OPTS)$<)
+TESTS += pmu-lpad
+run-pmu-lpad: pmu-lpad
+ $(call run-test, $<, $(QEMU) -cpu max -icount shift=0 $(QEMU_OPTS)$<)
+
RV32_CFLAGS = -march=rv32im_zicsr -mabi=ilp32
CLEANFILES += test-mcycle-rv32
diff --git a/tests/tcg/riscv64/pmu-lpad.S b/tests/tcg/riscv64/pmu-lpad.S
new file mode 100644
index
0000000000000000000000000000000000000000..11a900fef2850b6c478126f123bd036d4a258977
--- /dev/null
+++ b/tests/tcg/riscv64/pmu-lpad.S
@@ -0,0 +1,69 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* An LPAD fault must be deliverable with HPM instruction counting enabled. */
+
+ .option norvc
+ .option norelax
+
+ .text
+ .global _start
+_start:
+ lla t0, trap
+ csrw mtvec, t0
+ li t0, 2 /* HW_INSTRUCTIONS */
+ csrw mhpmevent3, t0
+ li t0, 1 << 10 /* mseccfg.MLPE */
+ csrs 0x747, t0
+
+ /* x7[31:12] = 0 does not match the nonzero LPAD label. */
+ li t2, 0
+ lla a0, target
+ jalr ra, a0, 0
+ j fail
+
+ .balign 4
+target:
+ .word 0x00001017 /* lpad 1 */
+ /*
+ * Keep the LPAD check inside a multi-instruction TB. Its exception
+ * helper must not read icount before leaving generated code.
+ */
+ .rept 16
+ nop
+ .endr
+ j fail
+
+trap:
+ csrr t0, mcause
+ li t1, 18 /* Software-check exception */
+ bne t0, t1, fail
+ csrr t0, mtval
+ li t1, 2 /* Landing-pad fault */
+ bne t0, t1, fail
+ csrr t0, mepc
+ lla t1, target
+ bne t0, t1, fail
+ li a0, 0
+ j exit
+
+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
diff --git a/tests/tcg/riscv64/test-minstret-ecall.S
b/tests/tcg/riscv64/test-minstret-ecall.S
index
ab268f7f22985820f19bde353215385608643f3a..b1857543d488df984b923ad1c135edb35cb948c7
100644
--- a/tests/tcg/riscv64/test-minstret-ecall.S
+++ b/tests/tcg/riscv64/test-minstret-ecall.S
@@ -18,11 +18,37 @@ _start:
li t1, 1
bne t0, t1, fail
+ /*
+ * minstret and a counter selecting HW_INSTRUCTIONS must both exclude
+ * ECALL, so they must contain the same number of retired instructions.
+ */
+ li t0, 12 /* mcountinhibit.IR | mcountinhibit.HPM3 */
+ csrs mcountinhibit, t0
+ csrw minstret, zero
+ csrw mhpmcounter3, zero
+ li t0, 2 /* RISCV_PMU_EVENT_HW_INSTRUCTIONS */
+ csrw mhpmevent3, t0
+ lla t0, trap_hpm
+ csrw mtvec, t0
+ li t0, 12
+ csrc mcountinhibit, t0
+ ecall
+ bne s3, s4, fail
+
li a0, 0
j _exit
trap:
csrr s1, minstret
+ j trap_check
+
+trap_hpm:
+ li t0, 12
+ csrs mcountinhibit, t0
+ csrr s3, minstret
+ csrr s4, mhpmcounter3
+
+trap_check:
csrr t0, mcause
li t1, 11 /* Environment call from M-mode */
bne t0, t1, fail
--
2.43.0