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/pmu-lpad.S | 72 +++++++++++++++++++++++++++++++++
tests/tcg/riscv64/system/meson.build | 7 ++++
tests/tcg/riscv64/test-minstret-ecall.S | 26 ++++++++++++
4 files changed, 133 insertions(+), 7 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/pmu-lpad.S b/tests/tcg/riscv64/pmu-lpad.S
new file mode 100644
index
0000000000000000000000000000000000000000..40a6b34b7c2b71dd42e78b6873d72df4d508289d
--- /dev/null
+++ b/tests/tcg/riscv64/pmu-lpad.S
@@ -0,0 +1,72 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR number for older assemblers. */
+#define CSR_MSECCFG 0x747
+
+/* 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 CSR_MSECCFG, 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/system/meson.build
b/tests/tcg/riscv64/system/meson.build
index
13ee7954ef4f09559a02a3730fb017ccf2cfd882..d2355090121ba7a7d0046c1fc1824d6a89c1267f
100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -61,6 +61,13 @@ tests += {
}
}
+tests += {
+ 'pmu-lpad.S': {
+ 'cflags': cflags,
+ 'qemu_args': ['-cpu', 'max', '-icount', 'shift=0', qemu_args],
+ },
+}
+
# Exercise RV32 CSRs with the RV64 emulator's 64-bit target_ulong.
tests += {
'../riscv32/smcdeleg-rv32.S': {
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