On Thu, Sep 10, 2026 at 10:39:38PM +0800, TANG Tiancheng wrote:
> Changing mhpmevent can lose pending cycle/instruction counts or leave a
> new fixed source without a baseline and overflow timer.
>
> Account for the old source before replacing the selector, then establish
> the enabled counter's new baseline and timer. Apply this to direct and
> indirect writes.
>
> Test overflow after initializing a counter with event zero and then
> selecting instructions.
>
> Fixes: 14664483457b ("target/riscv: Add sscofpmf extension support")
> Signed-off-by: TANG Tiancheng <[email protected]>
> Reviewed-by: Daniel Henrique Barboza <[email protected]>
Reviewed-by: Chao Liu <[email protected]>
Thanks,
Chao
> ---
> target/riscv/tcg/csr.c | 73
> +++++++++++++++++++++++++----------
> tests/tcg/riscv64/sscofpmf-overflow.S | 60 ++++++++++++++++++++++++++++
> tests/tcg/riscv64/system/meson.build | 7 ++++
> 3 files changed, 119 insertions(+), 21 deletions(-)
>
> diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
> index
> 65985efb220c80023cfd9d08e1878a19342aa879..52664a26f5a97a5dc8ff37abf99b4d10927fb120
> 100644
> --- a/target/riscv/tcg/csr.c
> +++ b/target/riscv/tcg/csr.c
> @@ -1209,23 +1209,58 @@ static RISCVException
> write_minstretcfgh(CPURISCVState *env, int csrno,
> static RISCVException read_mhpmevent(CPURISCVState *env, int csrno,
> target_ulong *val)
> {
> - int evt_index = csrno - CSR_MCOUNTINHIBIT;
> + int ctr_idx = csrno - CSR_MCOUNTINHIBIT;
> bool rv32 = riscv_cpu_mxl(env) == MXL_RV32;
>
> - *val = extract64(env->mhpmevent_val[evt_index], 0, rv32 ? 32 : 64);
> + *val = extract64(env->mhpmevent_val[ctr_idx], 0, rv32 ? 32 : 64);
>
> return RISCV_EXCP_NONE;
> }
>
> +static uint64_t riscv_pmu_ctr_get_fixed_counters_val(CPURISCVState *env,
> + int counter_idx);
> +
> +static void riscv_pmu_write_mhpmevent(CPURISCVState *env,
> + uint32_t ctr_idx, uint64_t value)
> +{
> + PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
> + bool enabled = !get_field(env->mcountinhibit, BIT(ctr_idx));
> +
> + /*
> + * A programmable counter backed by a fixed source uses mhpmcounter_val
> + * as its base and mhpmcounter_prev as the source snapshot. Preserve the
> + * visible value before changing the source or its privilege filters.
> + */
> + if (enabled &&
> + (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
> + riscv_pmu_ctr_monitor_instructions(env, ctr_idx))) {
> + uint64_t source = riscv_pmu_ctr_get_fixed_counters_val(env,
> + ctr_idx);
> +
> + counter->mhpmcounter_val += source - counter->mhpmcounter_prev;
> + }
> +
> + env->mhpmevent_val[ctr_idx] = value;
> + riscv_pmu_update_event_map(env, value, ctr_idx);
> +
> + if (enabled &&
> + (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
> + riscv_pmu_ctr_monitor_instructions(env, ctr_idx))) {
> + counter->mhpmcounter_prev =
> + riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx);
> + riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
> + }
> +}
> +
> static RISCVException write_mhpmevent(CPURISCVState *env, int csrno,
> target_ulong val, uintptr_t ra)
> {
> - int evt_index = csrno - CSR_MCOUNTINHIBIT;
> + int ctr_idx = csrno - CSR_MCOUNTINHIBIT;
> uint64_t mhpmevt_val;
> uint64_t inh_avail_mask;
>
> if (riscv_cpu_mxl(env) == MXL_RV32) {
> - mhpmevt_val = deposit64(env->mhpmevent_val[evt_index], 0, 32, val);
> + mhpmevt_val = deposit64(env->mhpmevent_val[ctr_idx], 0, 32, val);
> } else {
> inh_avail_mask = ~MHPMEVENT_FILTER_MASK | MHPMEVENT_BIT_MINH;
> inh_avail_mask |= riscv_has_ext(env, RVU) ? MHPMEVENT_BIT_UINH : 0;
> @@ -1237,8 +1272,7 @@ static RISCVException write_mhpmevent(CPURISCVState
> *env, int csrno,
> mhpmevt_val = val & inh_avail_mask;
> }
>
> - env->mhpmevent_val[evt_index] = mhpmevt_val;
> - riscv_pmu_update_event_map(env, mhpmevt_val, evt_index);
> + riscv_pmu_write_mhpmevent(env, ctr_idx, mhpmevt_val);
>
> return RISCV_EXCP_NONE;
> }
> @@ -1246,9 +1280,9 @@ static RISCVException write_mhpmevent(CPURISCVState
> *env, int csrno,
> static RISCVException read_mhpmeventh(CPURISCVState *env, int csrno,
> target_ulong *val)
> {
> - int evt_index = csrno - CSR_MHPMEVENT3H + 3;
> + int ctr_idx = csrno - CSR_MHPMEVENT3H + 3;
>
> - *val = extract64(env->mhpmevent_val[evt_index], 32, 32);
> + *val = extract64(env->mhpmevent_val[ctr_idx], 32, 32);
>
> return RISCV_EXCP_NONE;
> }
> @@ -1256,7 +1290,7 @@ static RISCVException read_mhpmeventh(CPURISCVState
> *env, int csrno,
> static RISCVException write_mhpmeventh(CPURISCVState *env, int csrno,
> target_ulong val, uintptr_t ra)
> {
> - int evt_index = csrno - CSR_MHPMEVENT3H + 3;
> + int ctr_idx = csrno - CSR_MHPMEVENT3H + 3;
> target_ulong inh_avail_mask = (target_ulong)(~MHPMEVENTH_FILTER_MASK |
> MHPMEVENTH_BIT_MINH);
>
> @@ -1267,10 +1301,9 @@ static RISCVException write_mhpmeventh(CPURISCVState
> *env, int csrno,
> inh_avail_mask |= (riscv_has_ext(env, RVH) &&
> riscv_has_ext(env, RVS)) ? MHPMEVENTH_BIT_VSINH : 0;
>
> - env->mhpmevent_val[evt_index] = deposit64(env->mhpmevent_val[evt_index],
> - 32, 32, val & inh_avail_mask);
> -
> - riscv_pmu_update_event_map(env, env->mhpmevent_val[evt_index],
> evt_index);
> + riscv_pmu_write_mhpmevent(env, ctr_idx,
> + deposit64(env->mhpmevent_val[ctr_idx], 32, 32,
> + val & inh_avail_mask));
>
> return RISCV_EXCP_NONE;
> }
> @@ -1512,11 +1545,11 @@ static int rmw_cd_mhpmcounterh(CPURISCVState *env,
> int ctr_idx,
> return 0;
> }
>
> -static int rmw_cd_mhpmevent(CPURISCVState *env, int evt_index,
> +static int rmw_cd_mhpmevent(CPURISCVState *env, int ctr_idx,
> target_ulong *val, target_ulong new_val,
> uint64_t wr_mask)
> {
> - uint64_t mhpmevt_val = env->mhpmevent_val[evt_index];
> + uint64_t mhpmevt_val = env->mhpmevent_val[ctr_idx];
>
> if (wr_mask != 0 && wr_mask != -1) {
> return -EINVAL;
> @@ -1531,8 +1564,7 @@ static int rmw_cd_mhpmevent(CPURISCVState *env, int
> evt_index,
> wr_mask &= ~MHPMEVENT_BIT_MINH;
> /* wr_mask is 64-bit so upper 32 bits of mhpmevt_val are retained */
> mhpmevt_val = (new_val & wr_mask) | (mhpmevt_val & ~wr_mask);
> - env->mhpmevent_val[evt_index] = mhpmevt_val;
> - riscv_pmu_update_event_map(env, mhpmevt_val, evt_index);
> + riscv_pmu_write_mhpmevent(env, ctr_idx, mhpmevt_val);
> } else {
> return -EINVAL;
> }
> @@ -1540,11 +1572,11 @@ static int rmw_cd_mhpmevent(CPURISCVState *env, int
> evt_index,
> return 0;
> }
>
> -static int rmw_cd_mhpmeventh(CPURISCVState *env, int evt_index,
> +static int rmw_cd_mhpmeventh(CPURISCVState *env, int ctr_idx,
> target_ulong *val, target_ulong new_val,
> target_ulong wr_mask)
> {
> - uint64_t mhpmevt_val = env->mhpmevent_val[evt_index];
> + uint64_t mhpmevt_val = env->mhpmevent_val[ctr_idx];
> uint32_t mhpmevth_val = extract64(mhpmevt_val, 32, 32);
>
> if (wr_mask != 0 && wr_mask != -1) {
> @@ -1560,8 +1592,7 @@ static int rmw_cd_mhpmeventh(CPURISCVState *env, int
> evt_index,
> wr_mask &= ~MHPMEVENTH_BIT_MINH;
> mhpmevth_val = (new_val & wr_mask) | (mhpmevth_val & ~wr_mask);
> mhpmevt_val = deposit64(mhpmevt_val, 32, 32, mhpmevth_val);
> - env->mhpmevent_val[evt_index] = mhpmevt_val;
> - riscv_pmu_update_event_map(env, mhpmevt_val, evt_index);
> + riscv_pmu_write_mhpmevent(env, ctr_idx, mhpmevt_val);
> } else {
> return -EINVAL;
> }
> diff --git a/tests/tcg/riscv64/sscofpmf-overflow.S
> b/tests/tcg/riscv64/sscofpmf-overflow.S
> new file mode 100644
> index
> 0000000000000000000000000000000000000000..97f03037bbfdd44f2288b257afb91499e4534f13
> --- /dev/null
> +++ b/tests/tcg/riscv64/sscofpmf-overflow.S
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> + .option norvc
> + .option norelax
> +
> + .text
> + .global _start
> +_start:
> + /* Program hpmcounter3 while no event is selected. */
> + csrw mhpmevent3, zero
> + li t0, -256
> + csrw mhpmcounter3, t0
> +
> + /* Start counting retired instructions with overflow enabled. */
> + li t0, 2
> + csrw mhpmevent3, t0
> +
> + /* Cross the 64-bit unsigned overflow boundary. */
> + .rept 1024
> + nop
> + .endr
> +
> + /* OF must be sticky and LCOFIP must pend even with LCOFIE clear. */
> + li t4, 0
> + csrr t0, mhpmevent3
> + srli t1, t0, 63
> + xori t1, t1, 1
> + or t4, t4, t1
> +
> + csrr t0, mip
> + li t1, 1 << 13
> + and t0, t0, t1
> + sltu t0, zero, t0
> + xori t0, t0, 1
> + or t4, t4, t0
> +
> + /* The counter wraps and continues counting after overflow. */
> + csrr t0, mhpmcounter3
> + li t1, -256
> + sltu t0, t0, t1
> + xori t0, t0, 1
> + or t4, t4, t0
> +
> + lla a1, semiargs
> + li t0, 0x20026 /* ADP_Stopped_ApplicationExit */
> + sd t0, 0(a1)
> + sd t4, 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
> 8604c2a45a9ad6bf8589f90d5b0d8fd1b2736db4..ebe78200fd551b42d3c99ae19ca03803797f803d
> 100644
> --- a/tests/tcg/riscv64/system/meson.build
> +++ b/tests/tcg/riscv64/system/meson.build
> @@ -61,6 +61,13 @@ tests += {
> }
> }
>
> +tests += {
> + 'sscofpmf-overflow.S': {
> + 'cflags': cflags,
> + 'qemu_args': ['-cpu', 'max', '-icount', 'shift=0', qemu_args],
> + },
> +}
> +
> if 'qemu-system-riscv64' in emulators
> tcg_tests += {
> 'riscv64-softmmu': {
>
> --
> 2.43.0
>