All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/14] RISC-V TCG PMU correctness fixes
@ 2026-09-10 14:39 TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes TANG Tiancheng
                   ` (13 more replies)
  0 siblings, 14 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

This series fixes RISC-V TCG PMU accounting, overflow notification, and
migration. It is based on v2 of Zephyr Li's "target/riscv: fix RV32
fixed counter accesses" [1], which is not included here.

The patches allow multiple HPM counters to select the same event and
preserve counts across selector, privilege-filter, and inhibit writes.
They use consistent cycle/instruction sources and compute the shared
overflow deadline from all eligible counters. Timer callbacks queue
checks on the owner vCPU instead of racing MTTCG execution.

Migration saves counter values including pending increments and restores
destination-local source baselines, event mappings, and overflow
scheduling. A new cpu/pmu-fixed subsection carries mcyclecfg/minstretcfg
and identifies this format. All TCG CPUs require the subsection; loading
older TCG streams without it is rejected. Source and destination still
need compatible CPU configurations. KVM PMU migration is unchanged.

The reset changes preserve the final counts in the old privilege/V mode
before entering M-mode with V=0.

TCG tests cover RV32/RV64 counter accesses, delegated registers, selector
and filter changes, multiple counters per event, overflow notification
with and without Sscofpmf, instruction exceptions, and reset. The RV32
HPM tests also cover full-width accesses through the shared counter path.

[1] https://lore.kernel.org/qemu-devel/20260909072356.42784-1-fritchleybohrer@gmail.com/
Based-on: <20260909072356.42784-1-fritchleybohrer@gmail.com>

Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
Changes in v2:
- Rebase on current master and Zephyr Li's RV32 fix v2.
- Register the system TCG tests with Meson and use -bios loading.
- Merge smcdeleg-counter-rv32.S and smcdeleg-event-rv32.S into
  smcdeleg-rv32.S (patch 6).
- Use CSR names or local CSR-number macros in tests.
- Use UINT32_MAX for delegated high-half write-mask checks (patch 6).
  The caller already requires XLEN=32.
- Link to v1: https://lore.kernel.org/qemu-devel/20260907-riscv-pmu-correctness-v1-0-5f1f41458989@linux.alibaba.com

---
TANG Tiancheng (14):
      target/riscv: Preserve PMU state across event selector writes
      target/riscv: Support multiple counters per PMU event
      target/riscv: Use VM-elapsed sources for fixed PMU events
      target/riscv: Preserve MINH on delegated config reads
      target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes
      target/riscv: Fix RV32 accesses to delegated PMU registers
      target/riscv: Preserve fixed counters across PMU state changes
      target/riscv: Require Sscofpmf for non-fixed event overflow
      target/riscv: Rebuild fixed-event PMU overflow deadlines
      target/riscv: Apply minstret exception accounting to HPM counters
      target/riscv: Process PMU timer expiry on the owner vCPU
      target/riscv: Migrate fixed PMU counter state
      target/riscv: Clear virtualization mode on reset
      target/riscv: Preserve fixed PMU state across reset

 system/cpu-timers.c                         |   4 +-
 system/cpus.c                               |   6 +-
 target/riscv/cpu.c                          |  17 +
 target/riscv/cpu.h                          |  14 +-
 target/riscv/machine.c                      |  92 +++-
 target/riscv/tcg/cpu_helper.c               |   2 +
 target/riscv/tcg/csr.c                      | 278 +++-------
 target/riscv/tcg/pmu.c                      | 793 +++++++++++++++++++---------
 target/riscv/tcg/pmu.h                      |  29 +-
 target/riscv/tcg/tcg-cpu.c                  |  10 +
 tests/tcg/riscv32/pmu-fixed-rv32.S          |  90 ++++
 tests/tcg/riscv32/pmu-minstretcfg-rv32.S    |  52 ++
 tests/tcg/riscv32/smcdeleg-minh-rv32.S      |  78 +++
 tests/tcg/riscv32/smcdeleg-rv32.S           | 115 ++++
 tests/tcg/riscv32/sscofpmf-event-rv32.S     |  99 ++++
 tests/tcg/riscv32/system/meson.build        |  35 ++
 tests/tcg/riscv64/pmu-cycle-controls.S      | 107 ++++
 tests/tcg/riscv64/pmu-lpad.S                |  72 +++
 tests/tcg/riscv64/pmu-reset-vs.S            | 105 ++++
 tests/tcg/riscv64/smcdeleg-minh.S           |  78 +++
 tests/tcg/riscv64/smcdeleg-sxl32.S          | 233 ++++++++
 tests/tcg/riscv64/sscofpmf-cycle-overflow.S |  58 ++
 tests/tcg/riscv64/sscofpmf-event-overflow.S | 103 ++++
 tests/tcg/riscv64/sscofpmf-overflow.S       | 134 +++++
 tests/tcg/riscv64/system/meson.build        |  88 +++
 tests/tcg/riscv64/test-minstret-ecall.S     |  26 +
 26 files changed, 2252 insertions(+), 466 deletions(-)
---
base-commit: 1df256f5968e9f7c3c4533a1383b071c044a36d6
change-id: 20260827-riscv-pmu-correctness-fa880fd9c48d
prerequisite-message-id: <20260909072356.42784-1-fritchleybohrer@gmail.com>
prerequisite-patch-id: f7a8b2a8cc67f360d6abb1adf002b000e7b2eb4d

Best regards,
-- 
TANG Tiancheng <lyndra@linux.alibaba.com>



^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-11  3:47   ` Chao Liu
  2026-09-10 14:39 ` [PATCH v2 02/14] target/riscv: Support multiple counters per PMU event TANG Tiancheng
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

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 <lyndra@linux.alibaba.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
---
 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



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 02/14] target/riscv: Support multiple counters per PMU event
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-11  3:54   ` Chao Liu
  2026-09-10 14:39 ` [PATCH v2 03/14] target/riscv: Use VM-elapsed sources for fixed PMU events TANG Tiancheng
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

The PMU FDT lists multiple eligible counters for each event, but the
event map stores only one counter per event. A second selector for the
same event is accepted by the CSR but ignored by the map, so its counter
does not count or overflow. Changing a selector between nonzero events
also leaves the old mapping.

Store a counter mask per event and rebuild the map from mhpmevent CSRs
after selector writes and migration. Update event delivery, fixed-source
accounting and overflow handling to cover every mapped counter.

Test selector replacement and multiple counters selecting instructions
or DTLB misses.

Fixes: 14664483457b ("target/riscv: Add sscofpmf extension support")
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
---
 target/riscv/machine.c                |   6 ++
 target/riscv/tcg/csr.c                |   2 +-
 target/riscv/tcg/pmu.c                | 182 +++++++++++++++++-----------------
 target/riscv/tcg/pmu.h                |   3 +-
 tests/tcg/riscv64/sscofpmf-overflow.S |  80 ++++++++++++++-
 5 files changed, 176 insertions(+), 97 deletions(-)

diff --git a/target/riscv/machine.c b/target/riscv/machine.c
index bf203bffcefb32710ed0f2af4d4f4595e122d1d9..b0ff2fc7f2ac10fab1f2ff845a953649091e1f43 100644
--- a/target/riscv/machine.c
+++ b/target/riscv/machine.c
@@ -24,6 +24,9 @@
 #include "migration/cpu.h"
 #include "exec/icount.h"
 #include "target/riscv/tcg/debug.h"
+#ifdef CONFIG_TCG
+#include "target/riscv/tcg/pmu.h"
+#endif
 #ifdef CONFIG_KVM
 #include "kvm/kvm_riscv.h"
 #endif
@@ -311,6 +314,9 @@ static int riscv_cpu_post_load(void *opaque, int version_id)
     CPURISCVState *env = &cpu->env;
 
     env->xl = cpu_recompute_xl(env);
+#ifdef CONFIG_TCG
+    riscv_pmu_rebuild_event_map(env);
+#endif
     return 0;
 }
 
diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index 52664a26f5a97a5dc8ff37abf99b4d10927fb120..d15a2d096cb6e13cd123ff9ae82ee7c643c2a961 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1241,7 +1241,7 @@ static void riscv_pmu_write_mhpmevent(CPURISCVState *env,
     }
 
     env->mhpmevent_val[ctr_idx] = value;
-    riscv_pmu_update_event_map(env, value, ctr_idx);
+    riscv_pmu_rebuild_event_map(env);
 
     if (enabled &&
         (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 1a4658319b11a9a8a0edef18fc8a5abd0027eb31..f19f417e90e33a94d00007ef132ef4e154175b19 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -49,6 +49,17 @@ static bool riscv_pmu_counter_enabled(RISCVCPU *cpu, uint32_t ctr_idx)
     }
 }
 
+static uint32_t riscv_pmu_event_counter_mask(RISCVCPU *cpu,
+                                             uint32_t event_idx)
+{
+    if (!cpu->pmu_event_ctr_map) {
+        return 0;
+    }
+
+    return GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
+                                                GUINT_TO_POINTER(event_idx)));
+}
+
 static bool riscv_pmu_counter_filtered(CPURISCVState *env, uint64_t cfg)
 {
     bool virt_on = env->virt_enabled;
@@ -180,41 +191,41 @@ void riscv_pmu_decr_instret(CPURISCVState *env)
 
 int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
 {
-    uint32_t ctr_idx;
+    uint32_t ctr_idx, ctr_mask;
     CPURISCVState *env = &cpu->env;
     uint64_t max_val = UINT64_MAX;
     PMUCTRState *counter;
-    gpointer value;
 
     if (!cpu->cfg.pmu_mask) {
         return 0;
     }
-    value = g_hash_table_lookup(cpu->pmu_event_ctr_map,
-                                GUINT_TO_POINTER(event_idx));
-    if (!value) {
-        return -1;
-    }
 
-    ctr_idx = GPOINTER_TO_UINT(value);
-    if (!riscv_pmu_counter_enabled(cpu, ctr_idx)) {
+    ctr_mask = riscv_pmu_event_counter_mask(cpu, event_idx);
+    if (!ctr_mask) {
         return -1;
     }
 
-    if (riscv_pmu_counter_filtered(env, env->mhpmevent_val[ctr_idx])) {
-        return 0;
-    }
+    while (ctr_mask) {
+        ctr_idx = ctz32(ctr_mask);
+        ctr_mask &= ~BIT(ctr_idx);
 
-    /* Handle the overflow scenario */
-    counter = &env->pmu_ctrs[ctr_idx];
-    if (counter->mhpmcounter_val == max_val) {
-        counter->mhpmcounter_val = 0;
-        /* Generate interrupt only if OF bit is clear */
-        if (!(env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF)) {
-            env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
-            riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
+        if (!riscv_pmu_counter_enabled(cpu, ctr_idx) ||
+            riscv_pmu_counter_filtered(env, env->mhpmevent_val[ctr_idx])) {
+            continue;
+        }
+
+        /* Handle the overflow scenario */
+        counter = &env->pmu_ctrs[ctr_idx];
+        if (counter->mhpmcounter_val == max_val) {
+            counter->mhpmcounter_val = 0;
+            /* Generate interrupt only if OF bit is clear */
+            if (!(env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF)) {
+                env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
+                riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
+            }
+        } else {
+            counter->mhpmcounter_val++;
         }
-    } else {
-        counter->mhpmcounter_val++;
     }
 
     return 0;
@@ -224,8 +235,7 @@ bool riscv_pmu_ctr_monitor_instructions(CPURISCVState *env,
                                         uint32_t target_ctr)
 {
     RISCVCPU *cpu;
-    uint32_t event_idx;
-    uint32_t ctr_idx;
+    uint32_t ctr_mask;
 
     /* Fixed instret counter */
     if (target_ctr == 2) {
@@ -237,21 +247,15 @@ bool riscv_pmu_ctr_monitor_instructions(CPURISCVState *env,
         return false;
     }
 
-    event_idx = RISCV_PMU_EVENT_HW_INSTRUCTIONS;
-    ctr_idx = GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
-                               GUINT_TO_POINTER(event_idx)));
-    if (!ctr_idx) {
-        return false;
-    }
-
-    return target_ctr == ctr_idx ? true : false;
+    ctr_mask = riscv_pmu_event_counter_mask(cpu,
+                                            RISCV_PMU_EVENT_HW_INSTRUCTIONS);
+    return (ctr_mask & BIT(target_ctr)) != 0;
 }
 
 bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env, uint32_t target_ctr)
 {
     RISCVCPU *cpu;
-    uint32_t event_idx;
-    uint32_t ctr_idx;
+    uint32_t ctr_mask;
 
     /* Fixed mcycle counter */
     if (target_ctr == 0) {
@@ -263,22 +267,23 @@ bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env, uint32_t target_ctr)
         return false;
     }
 
-    event_idx = RISCV_PMU_EVENT_HW_CPU_CYCLES;
-    ctr_idx = GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
-                               GUINT_TO_POINTER(event_idx)));
-
-    /* Counter zero is not used for event_ctr_map */
-    if (!ctr_idx) {
-        return false;
-    }
-
-    return (target_ctr == ctr_idx) ? true : false;
+    ctr_mask = riscv_pmu_event_counter_mask(cpu,
+                                            RISCV_PMU_EVENT_HW_CPU_CYCLES);
+    return (ctr_mask & BIT(target_ctr)) != 0;
 }
 
-static gboolean pmu_remove_event_map(gpointer key, gpointer value,
-                                     gpointer udata)
+static bool riscv_pmu_event_supported(uint32_t event_idx)
 {
-    return (GPOINTER_TO_UINT(value) == GPOINTER_TO_UINT(udata)) ? true : false;
+    switch (event_idx) {
+    case RISCV_PMU_EVENT_HW_CPU_CYCLES:
+    case RISCV_PMU_EVENT_HW_INSTRUCTIONS:
+    case RISCV_PMU_EVENT_CACHE_DTLB_READ_MISS:
+    case RISCV_PMU_EVENT_CACHE_DTLB_WRITE_MISS:
+    case RISCV_PMU_EVENT_CACHE_ITLB_PREFETCH_MISS:
+        return true;
+    default:
+        return false;
+    }
 }
 
 static int64_t pmu_icount_ticks_to_ns(int64_t value)
@@ -294,48 +299,32 @@ static int64_t pmu_icount_ticks_to_ns(int64_t value)
     return ret;
 }
 
-int riscv_pmu_update_event_map(CPURISCVState *env, uint64_t value,
-                               uint32_t ctr_idx)
+void riscv_pmu_rebuild_event_map(CPURISCVState *env)
 {
-    uint32_t event_idx;
+    uint32_t ctr_idx, ctr_mask, event_idx;
     RISCVCPU *cpu = env_archcpu(env);
 
-    if (!riscv_pmu_counter_valid(cpu, ctr_idx) || !cpu->pmu_event_ctr_map) {
-        return -1;
+    if (!cpu->pmu_event_ctr_map) {
+        return;
     }
 
-    /*
-     * Expected mhpmevent value is zero for reset case. Remove the current
-     * mapping.
-     */
-    if (!(value & MHPMEVENT_IDX_MASK)) {
-        g_hash_table_foreach_remove(cpu->pmu_event_ctr_map,
-                                    pmu_remove_event_map,
-                                    GUINT_TO_POINTER(ctr_idx));
-        return 0;
-    }
+    g_hash_table_remove_all(cpu->pmu_event_ctr_map);
+    for (ctr_idx = 3; ctr_idx < RV_MAX_MHPMCOUNTERS; ctr_idx++) {
+        if (!riscv_pmu_counter_valid(cpu, ctr_idx)) {
+            continue;
+        }
 
-    event_idx = value & MHPMEVENT_IDX_MASK;
-    if (g_hash_table_lookup(cpu->pmu_event_ctr_map,
-                            GUINT_TO_POINTER(event_idx))) {
-        return 0;
-    }
+        event_idx = env->mhpmevent_val[ctr_idx] & MHPMEVENT_IDX_MASK;
+        if (!event_idx || !riscv_pmu_event_supported(event_idx)) {
+            continue;
+        }
 
-    switch (event_idx) {
-    case RISCV_PMU_EVENT_HW_CPU_CYCLES:
-    case RISCV_PMU_EVENT_HW_INSTRUCTIONS:
-    case RISCV_PMU_EVENT_CACHE_DTLB_READ_MISS:
-    case RISCV_PMU_EVENT_CACHE_DTLB_WRITE_MISS:
-    case RISCV_PMU_EVENT_CACHE_ITLB_PREFETCH_MISS:
-        break;
-    default:
-        /* We don't support any raw events right now */
-        return -1;
+        ctr_mask = riscv_pmu_event_counter_mask(cpu, event_idx);
+        ctr_mask |= BIT(ctr_idx);
+        g_hash_table_insert(cpu->pmu_event_ctr_map,
+                            GUINT_TO_POINTER(event_idx),
+                            GUINT_TO_POINTER(ctr_mask));
     }
-    g_hash_table_insert(cpu->pmu_event_ctr_map, GUINT_TO_POINTER(event_idx),
-                        GUINT_TO_POINTER(ctr_idx));
-
-    return 0;
 }
 
 static bool pmu_hpmevent_set_of_if_clear(CPURISCVState *env, uint32_t ctr_idx)
@@ -348,23 +337,14 @@ static bool pmu_hpmevent_set_of_if_clear(CPURISCVState *env, uint32_t ctr_idx)
     }
 }
 
-static void pmu_timer_trigger_irq(RISCVCPU *cpu,
-                                  enum riscv_pmu_event_idx evt_idx)
+static void pmu_timer_trigger_irq_counter(RISCVCPU *cpu, uint32_t ctr_idx)
 {
-    uint32_t ctr_idx;
     CPURISCVState *env = &cpu->env;
     PMUCTRState *counter;
     int64_t irq_trigger_at;
     uint64_t curr_ctr_val, curr_ctrh_val;
     uint64_t ctr_val;
 
-    if (evt_idx != RISCV_PMU_EVENT_HW_CPU_CYCLES &&
-        evt_idx != RISCV_PMU_EVENT_HW_INSTRUCTIONS) {
-        return;
-    }
-
-    ctr_idx = GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
-                               GUINT_TO_POINTER(evt_idx)));
     if (!riscv_pmu_counter_enabled(cpu, ctr_idx)) {
         return;
     }
@@ -408,6 +388,26 @@ static void pmu_timer_trigger_irq(RISCVCPU *cpu,
     }
 }
 
+static void pmu_timer_trigger_irq(RISCVCPU *cpu,
+                                  enum riscv_pmu_event_idx evt_idx)
+{
+    uint32_t ctr_idx;
+    uint32_t ctr_mask;
+
+    if (evt_idx != RISCV_PMU_EVENT_HW_CPU_CYCLES &&
+        evt_idx != RISCV_PMU_EVENT_HW_INSTRUCTIONS) {
+        return;
+    }
+
+    ctr_mask = riscv_pmu_event_counter_mask(cpu, evt_idx);
+
+    while (ctr_mask) {
+        ctr_idx = ctz32(ctr_mask);
+        ctr_mask &= ~BIT(ctr_idx);
+        pmu_timer_trigger_irq_counter(cpu, ctr_idx);
+    }
+}
+
 /* Timer callback for instret and cycle counter overflow */
 void riscv_pmu_timer_cb(void *priv)
 {
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index 2429c01b776693ebb324ed63fee1feb56c821c21..910091690290cac9f77855f479bb9d90b2762efe 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -28,8 +28,7 @@ bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env,
                                   uint32_t target_ctr);
 void riscv_pmu_timer_cb(void *priv);
 void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
-int riscv_pmu_update_event_map(CPURISCVState *env, uint64_t value,
-                               uint32_t ctr_idx);
+void riscv_pmu_rebuild_event_map(CPURISCVState *env);
 int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx);
 void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name);
 int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value,
diff --git a/tests/tcg/riscv64/sscofpmf-overflow.S b/tests/tcg/riscv64/sscofpmf-overflow.S
index 97f03037bbfdd44f2288b257afb91499e4534f13..69626831344fe78317c3ca4b743c15ccf11b44b5 100644
--- a/tests/tcg/riscv64/sscofpmf-overflow.S
+++ b/tests/tcg/riscv64/sscofpmf-overflow.S
@@ -6,14 +6,18 @@
 	.text
 	.global _start
 _start:
-	/* Program hpmcounter3 while no event is selected. */
+	/* Program counters 3 and 4 while no event is selected. */
 	csrw	mhpmevent3, zero
 	li	t0, -256
 	csrw	mhpmcounter3, t0
+	csrw	mhpmevent4, zero
+	li	t0, -512
+	csrw	mhpmcounter4, t0
 
-	/* Start counting retired instructions with overflow enabled. */
+	/* Count the same event in both counters with overflow enabled. */
 	li	t0, 2
 	csrw	mhpmevent3, t0
+	csrw	mhpmevent4, t0
 
 	/* Cross the 64-bit unsigned overflow boundary. */
 	.rept	1024
@@ -26,6 +30,10 @@ _start:
 	srli	t1, t0, 63
 	xori	t1, t1, 1
 	or	t4, t4, t1
+	csrr	t0, mhpmevent4
+	srli	t1, t0, 63
+	xori	t1, t1, 1
+	or	t4, t4, t1
 
 	csrr	t0, mip
 	li	t1, 1 << 13
@@ -34,12 +42,68 @@ _start:
 	xori	t0, t0, 1
 	or	t4, t4, t0
 
-	/* The counter wraps and continues counting after overflow. */
+	/* Both counters wrap and continue counting after overflow. */
 	csrr	t0, mhpmcounter3
 	li	t1, -256
 	sltu	t0, t0, t1
 	xori	t0, t0, 1
 	or	t4, t4, t0
+	csrr	t0, mhpmcounter4
+	li	t1, -512
+	sltu	t0, t0, t1
+	xori	t0, t0, 1
+	or	t4, t4, t0
+
+	/* After selecting write misses, read misses must not increment HPM3. */
+	csrw	mhpmevent3, zero
+	csrw	mhpmcounter3, zero
+	li	t0, 0x10019		/* DTLB read miss */
+	csrw	mhpmevent3, t0
+	li	t0, 0x1001b		/* DTLB write miss */
+	csrw	mhpmevent3, t0
+	sfence.vma
+	lla	t2, stale_probe
+	lw	t3, 0(t2)
+	csrr	t0, mhpmcounter3
+	or	t4, t4, t0
+
+	/* Both counters must count a DTLB read miss. */
+	csrw	mhpmevent3, zero
+	csrw	mhpmevent4, zero
+	csrw	mhpmcounter3, zero
+	csrw	mhpmcounter4, zero
+	li	t0, 0x10019		/* DTLB read miss */
+	csrw	mhpmevent3, t0
+	csrw	mhpmevent4, t0
+	sfence.vma
+	lla	t2, tlb_probe
+	lw	t3, 0(t2)
+	csrr	t0, mhpmcounter3
+	csrr	t1, mhpmcounter4
+	sltu	t2, zero, t0
+	xori	t2, t2, 1
+	or	t4, t4, t2
+	sltu	t2, zero, t1
+	xori	t2, t2, 1
+	or	t4, t4, t2
+	xor	t0, t0, t1
+	sltu	t0, zero, t0
+	or	t4, t4, t0
+
+	/* Disabling HPM3 must leave HPM4 counting the same event. */
+	csrr	t5, mhpmcounter3
+	csrr	t6, mhpmcounter4
+	csrw	mhpmevent3, zero
+	sfence.vma
+	lla	t2, tlb_probe2
+	lw	t3, 0(t2)
+	csrr	t0, mhpmcounter3
+	xor	t0, t0, t5
+	or	t4, t4, t0
+	csrr	t0, mhpmcounter4
+	sltu	t0, t6, t0
+	xori	t0, t0, 1
+	or	t4, t4, t0
 
 	lla	a1, semiargs
 	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
@@ -55,6 +119,16 @@ _start:
 	j	.
 
 	.data
+	/* Give each DTLB probe a separate page. */
+	.balign	4096
+stale_probe:
+	.word	0
+	.balign	4096
+tlb_probe:
+	.word	0
+	.balign	4096
+tlb_probe2:
+	.word	0
 	.balign	16
 semiargs:
 	.space	16

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 03/14] target/riscv: Use VM-elapsed sources for fixed PMU events
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 02/14] target/riscv: Support multiple counters per PMU event TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-11  3:55   ` Chao Liu
  2026-09-10 14:39 ` [PATCH v2 04/14] target/riscv: Preserve MINH on delegated config reads TANG Tiancheng
                   ` (10 subsequent siblings)
  13 siblings, 1 reply; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

Raw host ticks keep advancing while the VM is stopped. Use
cpus_get_elapsed_ticks() for cycles and non-icount instruction counting,
and retain icount_get_raw() for instructions under icount. Document that
cpu_get_ticks() returns its stored value while VM ticks are disabled.

Under icount, cycles are already virtual nanoseconds. Convert only raw
instruction counts when scheduling overflow, avoiding a second scaling
of cycle distances. Add a cycle-overflow regression with icount shift=3.

Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-10/msg00668.html
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
---
 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/sscofpmf-cycle-overflow.S | 58 +++++++++++++++++++++++++++++
 tests/tcg/riscv64/system/meson.build        |  7 ++++
 7 files changed, 103 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/sscofpmf-cycle-overflow.S b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
new file mode 100644
index 0000000000000000000000000000000000000000..846d4651c4ee8f06df83bed85512ab9794552c28
--- /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	mhpmevent3, zero
+	li	t0, -4096
+	csrw	mhpmcounter3, t0
+	li	t0, 1
+	csrw	mhpmevent3, t0		/* mhpmevent3: cycles */
+	csrr	t1, mcycle
+
+1:
+	csrr	t0, mhpmevent3
+	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
diff --git a/tests/tcg/riscv64/system/meson.build b/tests/tcg/riscv64/system/meson.build
index ebe78200fd551b42d3c99ae19ca03803797f803d..668a9a76070b6f16087b08ea84683d883312e951 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -68,6 +68,13 @@ tests += {
   },
 }
 
+tests += {
+  'sscofpmf-cycle-overflow.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', '-icount', 'shift=3', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv64' in emulators
   tcg_tests += {
     'riscv64-softmmu': {

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 04/14] target/riscv: Preserve MINH on delegated config reads
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (2 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 03/14] target/riscv: Use VM-elapsed sources for fixed PMU events TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-11  3:56   ` Chao Liu
  2026-09-10 14:39 ` [PATCH v2 05/14] target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes TANG Tiancheng
                   ` (9 subsequent siblings)
  13 siblings, 1 reply; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

Smcdeleg requires MINH to read as zero through sireg*. The RV64 callback
masks it by modifying the machine register; the RV32 high-half callback
does not mask it.

Return a masked copy without changing mcyclecfg or minstretcfg. Test both
configuration registers on RV32 and RV64.

Fixes: d9fa41e10156 ("target/riscv: Bugfix make bit 62 read-only 0 for sireg* cfg CSR read")
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/tcg/csr.c                 |  8 ++--
 tests/tcg/riscv32/smcdeleg-minh-rv32.S | 78 ++++++++++++++++++++++++++++++++++
 tests/tcg/riscv32/system/meson.build   |  7 +++
 tests/tcg/riscv64/smcdeleg-minh.S      | 78 ++++++++++++++++++++++++++++++++++
 tests/tcg/riscv64/system/meson.build   |  7 +++
 5 files changed, 174 insertions(+), 4 deletions(-)

diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index 60caee32dc0cf5b6a8492e0cf8ff15f71acc2087..57030724f85a897e21e5082b9857411b50f932ac 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1607,7 +1607,7 @@ static int rmw_cd_ctr_cfg(CPURISCVState *env, int cfg_index, target_ulong *val,
             wr_mask &= ~MCYCLECFG_BIT_MINH;
             env->mcyclecfg = (new_val & wr_mask) | (env->mcyclecfg & ~wr_mask);
         } else {
-            *val = env->mcyclecfg &= ~MHPMEVENT_BIT_MINH;
+            *val = env->mcyclecfg & ~MCYCLECFG_BIT_MINH;
         }
         break;
     case 2:             /* INSTRETCFG */
@@ -1616,7 +1616,7 @@ static int rmw_cd_ctr_cfg(CPURISCVState *env, int cfg_index, target_ulong *val,
             env->minstretcfg = (new_val & wr_mask) |
                                (env->minstretcfg & ~wr_mask);
         } else {
-            *val = env->minstretcfg &= ~MHPMEVENT_BIT_MINH;
+            *val = env->minstretcfg & ~MINSTRETCFG_BIT_MINH;
         }
         break;
     default:
@@ -1642,7 +1642,7 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
             cfgh = (new_val & wr_mask) | (cfgh & ~wr_mask);
             env->mcyclecfg = deposit64(env->mcyclecfg, 32, 32, cfgh);
         } else {
-            *val = cfgh;
+            *val = cfgh & ~MCYCLECFGH_BIT_MINH;
         }
         break;
     case 2:          /* INSTRETCFGH */
@@ -1652,7 +1652,7 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
             cfgh = (new_val & wr_mask) | (cfgh & ~wr_mask);
             env->minstretcfg = deposit64(env->minstretcfg, 32, 32, cfgh);
         } else {
-            *val = cfgh;
+            *val = cfgh & ~MINSTRETCFGH_BIT_MINH;
         }
         break;
     default:
diff --git a/tests/tcg/riscv32/smcdeleg-minh-rv32.S b/tests/tcg/riscv32/smcdeleg-minh-rv32.S
new file mode 100644
index 0000000000000000000000000000000000000000..db467b95cf1441e3440f59097401cf6956d294d3
--- /dev/null
+++ b/tests/tcg/riscv32/smcdeleg-minh-rv32.S
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR numbers for older assemblers. */
+#define CSR_SISELECT         0x150
+#define CSR_SIREG5           0x156
+#define CSR_MENVCFGH         0x31a
+#define CSR_MCYCLECFGH       0x721
+#define CSR_MINSTRETCFGH     0x722
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global	_start
+_start:
+	/*
+	 * Failure bits:
+	 * 0: delegated cyclecfgh exposes MINH
+	 * 1: reading delegated cyclecfgh clears mcyclecfgh.MINH
+	 * 2: delegated instretcfgh exposes MINH
+	 * 3: reading delegated instretcfgh clears minstretcfgh.MINH
+	 */
+	li	t4, 0
+	li	t0, 1
+	slli	t0, t0, 30		/* MINH in the high half */
+	csrw	CSR_MCYCLECFGH, t0
+	csrw	CSR_MINSTRETCFGH, t0
+	li	t1, 1
+	slli	t1, t1, 28		/* menvcfgh.CDE */
+	csrw	CSR_MENVCFGH, t1
+	li	t1, 5			/* Delegate cycle and instret. */
+	csrw	mcounteren, t1
+
+	/* Check the delegated view, then the underlying machine register. */
+	li	t1, 0x40		/* siselect: cycle */
+	csrw	CSR_SISELECT, t1
+	csrr	t1, CSR_SIREG5
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	or	t4, t4, t1
+	csrr	t1, CSR_MCYCLECFGH
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	xori	t1, t1, 1
+	slli	t1, t1, 1
+	or	t4, t4, t1
+
+	li	t1, 0x42		/* siselect: instret */
+	csrw	CSR_SISELECT, t1
+	csrr	t1, CSR_SIREG5
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	slli	t1, t1, 2
+	or	t4, t4, t1
+	csrr	t1, CSR_MINSTRETCFGH
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	xori	t1, t1, 1
+	slli	t1, t1, 3
+	or	t4, t4, t1
+
+	lla	a1, semiargs
+	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
+	sw	t0, 0(a1)
+	sw	t4, 4(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	8
diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
index 16f9a06c9485ed75b3d127d6b9d090f5eaad4486..cfe2d854b729a8dfdbce2373e73b91b470db6b4e 100644
--- a/tests/tcg/riscv32/system/meson.build
+++ b/tests/tcg/riscv32/system/meson.build
@@ -22,6 +22,13 @@ tests += {
   },
 }
 
+tests += {
+  'smcdeleg-minh-rv32.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv32' in emulators
   tcg_tests += {
     'riscv32-softmmu': {
diff --git a/tests/tcg/riscv64/smcdeleg-minh.S b/tests/tcg/riscv64/smcdeleg-minh.S
new file mode 100644
index 0000000000000000000000000000000000000000..38d3c30f0afaa416a46322f2b892e5969172a60f
--- /dev/null
+++ b/tests/tcg/riscv64/smcdeleg-minh.S
@@ -0,0 +1,78 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR numbers for older assemblers. */
+#define CSR_SISELECT         0x150
+#define CSR_SIREG2           0x152
+#define CSR_MENVCFG          0x30a
+#define CSR_MCYCLECFG        0x321
+#define CSR_MINSTRETCFG      0x322
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global	_start
+_start:
+	/*
+	 * Failure bits:
+	 * 0: delegated cyclecfg exposes MINH
+	 * 1: reading delegated cyclecfg clears mcyclecfg.MINH
+	 * 2: delegated instretcfg exposes MINH
+	 * 3: reading delegated instretcfg clears minstretcfg.MINH
+	 */
+	li	t4, 0
+	li	t0, 1
+	slli	t0, t0, 62		/* MINH */
+	csrw	CSR_MCYCLECFG, t0
+	csrw	CSR_MINSTRETCFG, t0
+	li	t1, 1
+	slli	t1, t1, 60		/* menvcfg.CDE */
+	csrw	CSR_MENVCFG, t1
+	li	t1, 5			/* Delegate cycle and instret. */
+	csrw	mcounteren, t1
+
+	/* Check the delegated view, then the underlying machine register. */
+	li	t1, 0x40		/* siselect: cycle */
+	csrw	CSR_SISELECT, t1
+	csrr	t1, CSR_SIREG2
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	or	t4, t4, t1
+	csrr	t1, CSR_MCYCLECFG
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	xori	t1, t1, 1
+	slli	t1, t1, 1
+	or	t4, t4, t1
+
+	li	t1, 0x42		/* siselect: instret */
+	csrw	CSR_SISELECT, t1
+	csrr	t1, CSR_SIREG2
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	slli	t1, t1, 2
+	or	t4, t4, t1
+	csrr	t1, CSR_MINSTRETCFG
+	and	t1, t1, t0
+	sltu	t1, zero, t1
+	xori	t1, t1, 1
+	slli	t1, t1, 3
+	or	t4, t4, t1
+
+	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 668a9a76070b6f16087b08ea84683d883312e951..5d91381c62d77ae7e37c129112d6367f692df660 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -75,6 +75,13 @@ tests += {
   },
 }
 
+tests += {
+  'smcdeleg-minh.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv64' in emulators
   tcg_tests += {
     'riscv64-softmmu': {

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 05/14] target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (3 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 04/14] target/riscv: Preserve MINH on delegated config reads TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-11  4:43   ` Chao Liu
  2026-09-10 14:39 ` [PATCH v2 06/14] target/riscv: Fix RV32 accesses to delegated PMU registers TANG Tiancheng
                   ` (8 subsequent siblings)
  13 siblings, 1 reply; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

RV32 write_minstretcfg() replaces the full 64-bit register, clearing
minstretcfgh and its privilege-inhibit bits.

Replace only bits 31:0, as write_mcyclecfg() does. Test that a low-half
write preserves both set and clear xINH bits in minstretcfgh.

Fixes: b54a84c15e38 ("target/riscv: Add cycle & instret privilege mode filtering support")
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/tcg/csr.c                   |  2 +-
 tests/tcg/riscv32/pmu-minstretcfg-rv32.S | 52 ++++++++++++++++++++++++++++++++
 tests/tcg/riscv32/system/meson.build     |  7 +++++
 3 files changed, 60 insertions(+), 1 deletion(-)

diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index 57030724f85a897e21e5082b9857411b50f932ac..f9f43a9c12e1afdbdf6c7202c167988389963c10 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1167,7 +1167,7 @@ static RISCVException write_minstretcfg(CPURISCVState *env, int csrno,
     uint64_t inh_avail_mask;
 
     if (riscv_cpu_mxl(env) == MXL_RV32) {
-        env->minstretcfg = val;
+        env->minstretcfg = deposit64(env->minstretcfg, 0, 32, val);
     } else {
         inh_avail_mask = ~MHPMEVENT_FILTER_MASK | MINSTRETCFG_BIT_MINH;
         inh_avail_mask |= riscv_has_ext(env, RVU) ? MINSTRETCFG_BIT_UINH : 0;
diff --git a/tests/tcg/riscv32/pmu-minstretcfg-rv32.S b/tests/tcg/riscv32/pmu-minstretcfg-rv32.S
new file mode 100644
index 0000000000000000000000000000000000000000..59ee516cea9d09d0945033a77e788e4387cd684f
--- /dev/null
+++ b/tests/tcg/riscv32/pmu-minstretcfg-rv32.S
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR numbers for older assemblers. */
+#define CSR_MINSTRETCFG      0x322
+#define CSR_MINSTRETCFGH     0x722
+
+/* RV32 writes to minstretcfg must preserve minstretcfgh. */
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global _start
+_start:
+	/*
+	 * Use complementary patterns to check both set and clear xINH bits.
+	 * All fields in the low half are WPRI, so write zero there.
+	 * Exit status 1 or 2 identifies the pattern that was not preserved.
+	 */
+	li	t4, 1
+	li	t0, 0x54000000		/* MINH, UINH, VUINH */
+	csrw	CSR_MINSTRETCFGH, t0
+	csrw	CSR_MINSTRETCFG, zero
+	csrr	t1, CSR_MINSTRETCFGH
+	bne	t0, t1, exit
+
+	li	t4, 2
+	li	t0, 0x28000000		/* SINH, VSINH */
+	csrw	CSR_MINSTRETCFGH, t0
+	csrw	CSR_MINSTRETCFG, zero
+	csrr	t1, CSR_MINSTRETCFGH
+	bne	t0, t1, exit
+	li	t4, 0
+
+exit:
+	lla	a1, semiargs
+	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
+	sw	t0, 0(a1)
+	sw	t4, 4(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	8
diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
index cfe2d854b729a8dfdbce2373e73b91b470db6b4e..5f417c0c51e17840072104812f5854dfb65d1d06 100644
--- a/tests/tcg/riscv32/system/meson.build
+++ b/tests/tcg/riscv32/system/meson.build
@@ -29,6 +29,13 @@ tests += {
   },
 }
 
+tests += {
+  'pmu-minstretcfg-rv32.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv32' in emulators
   tcg_tests += {
     'riscv32-softmmu': {

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 06/14] target/riscv: Fix RV32 accesses to delegated PMU registers
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (4 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 05/14] target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 07/14] target/riscv: Preserve fixed counters across PMU state changes TANG Tiancheng
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

Delegated PMU writes compare the full-write mask with -1 in its C type,
rejecting valid RV32 writes. Counter widths and high-half access checks
also use MXLEN, so RV32 S-mode on an RV64 CPU cannot access the high half
and low-half counter writes overwrite all 64 bits.

Use the current XLEN for delegated masks, counter widths and high-half
checks, as required by Smcsrind. Keep MXLEN for direct counter accesses
and retain the 64-bit selector merge mask to preserve the unwritten half.

Test RV32 CPUs in both system emulators and RV32 S-mode on an RV64 CPU,
including half preservation, machine MINH and RV64 M-mode alias accesses.

Fixes: 6247dc2ef70b ("target/riscv: Add counter delegation/configuration support")
Fixes: c9efdb7b63a4 ("target/riscv: Combine mhpmevent and mhpmeventh")
Link: https://docs.riscv.org/reference/isa/v20260120/priv/indirect-csr.html
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/tcg/csr.c               |  40 +++---
 target/riscv/tcg/pmu.c               |   6 +-
 target/riscv/tcg/pmu.h               |   3 +-
 tests/tcg/riscv32/smcdeleg-rv32.S    | 115 +++++++++++++++++
 tests/tcg/riscv32/system/meson.build |   7 ++
 tests/tcg/riscv64/smcdeleg-sxl32.S   | 233 +++++++++++++++++++++++++++++++++++
 tests/tcg/riscv64/system/meson.build |  31 +++++
 7 files changed, 412 insertions(+), 23 deletions(-)

diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index f9f43a9c12e1afdbdf6c7202c167988389963c10..ec6cc6081cb1aa43dc8ee0755b1a4eba1b63350d 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1357,10 +1357,10 @@ static uint64_t riscv_pmu_ctr_get_fixed_counters_val(CPURISCVState *env,
 }
 
 static RISCVException riscv_pmu_write_ctr(CPURISCVState *env, target_ulong val,
-                                          uint32_t ctr_idx)
+                                          uint32_t ctr_idx, RISCVMXL xl)
 {
     PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
-    bool rv32 = riscv_cpu_mxl(env) == MXL_RV32;
+    bool rv32 = xl == MXL_RV32;
     int deposit_size = rv32 ? 32 : 64;
     uint64_t ctr;
 
@@ -1418,7 +1418,7 @@ static RISCVException write_mhpmcounter(CPURISCVState *env, int csrno,
 {
     int ctr_idx = csrno - CSR_MCYCLE;
 
-    return riscv_pmu_write_ctr(env, val, ctr_idx);
+    return riscv_pmu_write_ctr(env, val, ctr_idx, riscv_cpu_mxl(env));
 }
 
 static RISCVException write_mhpmcounterh(CPURISCVState *env, int csrno,
@@ -1430,10 +1430,11 @@ static RISCVException write_mhpmcounterh(CPURISCVState *env, int csrno,
 }
 
 RISCVException riscv_pmu_read_ctr(CPURISCVState *env, target_ulong *val,
-                                  bool upper_half, uint32_t ctr_idx)
+                                  bool upper_half, uint32_t ctr_idx,
+                                  RISCVMXL xl)
 {
     PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
-    bool rv32 = riscv_cpu_mxl(env) == MXL_RV32;
+    bool rv32 = xl == MXL_RV32;
     int start = upper_half ? 32 : 0;
     int length = rv32 ? 32 : 64;
     uint64_t ctr_val;
@@ -1482,7 +1483,7 @@ static RISCVException read_hpmcounter(CPURISCVState *env, int csrno,
         return RISCV_EXCP_ILLEGAL_INST;
     }
 
-    return riscv_pmu_read_ctr(env, val, false, ctr_index);
+    return riscv_pmu_read_ctr(env, val, false, ctr_index, riscv_cpu_mxl(env));
 }
 
 static RISCVException read_hpmcounterh(CPURISCVState *env, int csrno,
@@ -1498,21 +1499,23 @@ static RISCVException read_hpmcounterh(CPURISCVState *env, int csrno,
         return RISCV_EXCP_ILLEGAL_INST;
     }
 
-    return riscv_pmu_read_ctr(env, val, true, ctr_index);
+    return riscv_pmu_read_ctr(env, val, true, ctr_index, riscv_cpu_mxl(env));
 }
 
 static int rmw_cd_mhpmcounter(CPURISCVState *env, int ctr_idx,
                               target_ulong *val, target_ulong new_val,
                               target_ulong wr_mask)
 {
-    if (wr_mask != 0 && wr_mask != -1) {
+    uint64_t xlen_mask = env->xl == MXL_RV32 ? UINT32_MAX : UINT64_MAX;
+
+    if (wr_mask != 0 && wr_mask != xlen_mask) {
         return -EINVAL;
     }
 
     if (!wr_mask && val) {
-        riscv_pmu_read_ctr(env, val, false, ctr_idx);
+        riscv_pmu_read_ctr(env, val, false, ctr_idx, env->xl);
     } else if (wr_mask) {
-        riscv_pmu_write_ctr(env, new_val, ctr_idx);
+        riscv_pmu_write_ctr(env, new_val, ctr_idx, env->xl);
     } else {
         return -EINVAL;
     }
@@ -1524,12 +1527,12 @@ static int rmw_cd_mhpmcounterh(CPURISCVState *env, int ctr_idx,
                                target_ulong *val, target_ulong new_val,
                                target_ulong wr_mask)
 {
-    if (wr_mask != 0 && wr_mask != -1) {
+    if (wr_mask != 0 && wr_mask != UINT32_MAX) {
         return -EINVAL;
     }
 
     if (!wr_mask && val) {
-        riscv_pmu_read_ctr(env, val, true, ctr_idx);
+        riscv_pmu_read_ctr(env, val, true, ctr_idx, env->xl);
     } else if (wr_mask) {
         riscv_pmu_write_ctrh(env, new_val, ctr_idx);
     } else {
@@ -1544,8 +1547,9 @@ static int rmw_cd_mhpmevent(CPURISCVState *env, int ctr_idx,
                             uint64_t wr_mask)
 {
     uint64_t mhpmevt_val = env->mhpmevent_val[ctr_idx];
+    uint64_t xlen_mask = env->xl == MXL_RV32 ? UINT32_MAX : UINT64_MAX;
 
-    if (wr_mask != 0 && wr_mask != -1) {
+    if (wr_mask != 0 && wr_mask != xlen_mask) {
         return -EINVAL;
     }
 
@@ -1573,7 +1577,7 @@ static int rmw_cd_mhpmeventh(CPURISCVState *env, int ctr_idx,
     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) {
+    if (wr_mask != 0 && wr_mask != UINT32_MAX) {
         return -EINVAL;
     }
 
@@ -1630,10 +1634,6 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
 {
     uint64_t cfgh;
 
-    if (riscv_cpu_mxl(env) != MXL_RV32) {
-        return RISCV_EXCP_ILLEGAL_INST;
-    }
-
     switch (cfg_index) {
     case 0:         /* CYCLECFGH */
         cfgh = extract64(env->mcyclecfg, 32, 32);
@@ -2809,9 +2809,9 @@ static int rmw_xireg_cd(CPURISCVState *env, int csrno,
         goto done;
     }
 
-    /* sireg4 and sireg5 provides access RV32 only CSRs */
+    /* Delegated high halves are accessible only when the current XLEN is 32. */
     if (((csrno == CSR_SIREG5) || (csrno == CSR_SIREG4)) &&
-        (riscv_cpu_mxl(env) != MXL_RV32)) {
+        env->xl != MXL_RV32) {
         ret = RISCV_EXCP_ILLEGAL_INST;
         goto done;
     }
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index ea0ffe41258d4dbef9dc952655e6301c14ba1d23..54fff2ba49c1034d5fa6db1c7b19ff59003cd1e1 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -372,10 +372,12 @@ static void pmu_timer_trigger_irq_counter(RISCVCPU *cpu, uint32_t ctr_idx)
         return;
     }
 
-    riscv_pmu_read_ctr(env, (target_ulong *)&curr_ctr_val, false, ctr_idx);
+    riscv_pmu_read_ctr(env, (target_ulong *)&curr_ctr_val, false, ctr_idx,
+                        riscv_cpu_mxl(env));
     ctr_val = counter->mhpmcounter_val;
     if (riscv_cpu_mxl(env) == MXL_RV32) {
-        riscv_pmu_read_ctr(env, (target_ulong *)&curr_ctrh_val, true, ctr_idx);
+        riscv_pmu_read_ctr(env, (target_ulong *)&curr_ctrh_val, true, ctr_idx,
+                            riscv_cpu_mxl(env));
         curr_ctr_val = curr_ctr_val | (curr_ctrh_val << 32);
     }
 
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index 339a4b3ac09c4a91cddd9250824284203b16b4fa..bf2e8373474d471d914f8801c55d2f6ffbb5cdd3 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -38,6 +38,7 @@ void riscv_pmu_update_fixed_ctrs(CPURISCVState *env, privilege_mode_t 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);
+                                  bool upper_half, uint32_t ctr_idx,
+                                  RISCVMXL xl);
 
 #endif /* RISCV_PMU_H */
diff --git a/tests/tcg/riscv32/smcdeleg-rv32.S b/tests/tcg/riscv32/smcdeleg-rv32.S
new file mode 100644
index 0000000000000000000000000000000000000000..7b7c1db5bd3cbb59397a573adf0f7d685a1db3d8
--- /dev/null
+++ b/tests/tcg/riscv32/smcdeleg-rv32.S
@@ -0,0 +1,115 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* RV32 delegated PMU registers, in both RV32 and RV64 emulators. */
+
+/* CSR numbers for older assemblers. */
+#define CSR_SISELECT         0x150
+#define CSR_SIREG            0x151
+#define CSR_SIREG2           0x152
+#define CSR_SIREG4           0x155
+#define CSR_SIREG5           0x156
+#define CSR_MENVCFGH         0x31a
+#define CSR_MHPMEVENT3H      0x723
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global	_start
+_start:
+	/* Unexpected exceptions report the current check number. */
+	li	t4, 1
+	lla	t0, fail
+	csrw	mtvec, t0
+	li	t0, 8			/* Stop HPM3 so reads are exact. */
+	csrw	mcountinhibit, t0
+	csrw	mcounteren, t0
+	li	t0, 1 << 28		/* menvcfgh.CDE */
+	csrw	CSR_MENVCFGH, t0
+	li	t0, 0x43		/* siselect: counter 3 */
+	csrw	CSR_SISELECT, t0
+	li	t0, 0x12345678
+	csrw	mhpmcounter3h, t0
+	li	t0, 1			/* HW_CPU_CYCLES */
+	csrw	mhpmevent3, t0
+	li	t0, 0xc0000000		/* OF | MINH */
+	csrw	CSR_MHPMEVENT3H, t0
+
+	/* sireg writes the low half without changing the high half. */
+	li	t0, 0x2468ace0
+	csrw	CSR_SIREG, t0
+	csrr	t1, CSR_SIREG
+	bne	t0, t1, fail
+	csrr	t1, mhpmcounter3
+	bne	t0, t1, fail
+	li	t0, 0x12345678
+	csrr	t1, mhpmcounter3h
+	bne	t0, t1, fail
+
+	/* sireg4 writes the high half without changing the low half. */
+	li	t4, 2
+	li	t0, 0xfedcba98
+	csrw	CSR_SIREG4, t0
+	csrr	t1, CSR_SIREG4
+	bne	t0, t1, fail
+	csrr	t1, mhpmcounter3h
+	bne	t0, t1, fail
+	li	t0, 0x2468ace0
+	csrr	t1, mhpmcounter3
+	bne	t0, t1, fail
+
+	/* sireg5 clears OF and sets SINH, but cannot change machine MINH. */
+	li	t4, 3
+	li	t0, 0x20000000		/* SINH */
+	csrw	CSR_SIREG5, t0
+	csrr	t1, CSR_SIREG5
+	bne	t0, t1, fail
+	li	t0, 0x60000000		/* MINH | SINH */
+	csrr	t1, CSR_MHPMEVENT3H
+	bne	t0, t1, fail
+	li	t0, 1
+	csrr	t1, mhpmevent3
+	bne	t0, t1, fail
+
+	/* sireg2 changes the event without changing those high-half bits. */
+	li	t4, 4
+	li	t0, 2			/* HW_INSTRUCTIONS */
+	csrw	CSR_SIREG2, t0
+	csrr	t1, mhpmevent3
+	bne	t0, t1, fail
+	li	t0, 0x60000000
+	csrr	t1, CSR_MHPMEVENT3H
+	bne	t0, t1, fail
+
+	/* Event replacement must also preserve OF when it is set. */
+	li	t4, 5
+	li	t0, 0xe0000000		/* OF | MINH | SINH */
+	csrw	CSR_MHPMEVENT3H, t0
+	li	t0, 1			/* HW_CPU_CYCLES */
+	csrw	mhpmevent3, t0
+	li	t0, 2			/* HW_INSTRUCTIONS */
+	csrw	CSR_SIREG2, t0
+	csrr	t1, mhpmevent3
+	bne	t0, t1, fail
+	li	t0, 0xe0000000
+	csrr	t1, CSR_MHPMEVENT3H
+	bne	t0, t1, fail
+
+	/* Selecting event zero must also leave the high half unchanged. */
+	li	t4, 6
+	csrw	CSR_SIREG2, zero
+	csrr	t1, mhpmevent3
+	bnez	t1, fail
+	csrr	t1, CSR_MHPMEVENT3H
+	bne	t0, t1, fail
+
+	li	t0, 0x5555		/* FINISHER_PASS */
+	j	finish
+fail:
+	slli	t0, t4, 16
+	li	t1, 0x3333		/* FINISHER_FAIL with check number */
+	or	t0, t0, t1
+finish:
+	li	t1, 0x100000		/* virt test device */
+	sw	t0, 0(t1)
+	j	.
diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
index 5f417c0c51e17840072104812f5854dfb65d1d06..800c754093e275cd25be7878a96a8c551f73dbce 100644
--- a/tests/tcg/riscv32/system/meson.build
+++ b/tests/tcg/riscv32/system/meson.build
@@ -36,6 +36,13 @@ tests += {
   },
 }
 
+tests += {
+  'smcdeleg-rv32.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv32' in emulators
   tcg_tests += {
     'riscv32-softmmu': {
diff --git a/tests/tcg/riscv64/smcdeleg-sxl32.S b/tests/tcg/riscv64/smcdeleg-sxl32.S
new file mode 100644
index 0000000000000000000000000000000000000000..3c5fc2b174c8f18988943f6b4bb9c07ca5dcd53d
--- /dev/null
+++ b/tests/tcg/riscv64/smcdeleg-sxl32.S
@@ -0,0 +1,233 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR numbers for older assemblers. */
+#define CSR_SISELECT         0x150
+#define CSR_SIREG            0x151
+#define CSR_SIREG2           0x152
+#define CSR_SIREG4           0x155
+#define CSR_SIREG5           0x156
+#define CSR_MENVCFG          0x30a
+#define CSR_MSTATEEN0        0x30c
+#define CSR_MCYCLECFG        0x321
+#define CSR_MINSTRETCFG      0x322
+
+/*
+ * Exercise delegated PMU registers with MXLEN=64 and SXLEN=32.
+ * Build separately for counter low halves, counter high halves and configs
+ * so a failure in one access path does not hide failures in the others.
+ */
+
+	.option	norvc
+	.option	norelax
+
+	/* RV64 'li' can emit ADDIW, which is illegal in the RV32 blocks. */
+	.macro	load32 reg, value
+	lui	\reg, %hi(\value)
+	addi	\reg, \reg, %lo(\value)
+	.endm
+
+	/* Enter RV32 S-mode; the ECALL trap below returns to M-mode. */
+	.macro	enter_s32 label
+	li	a0, 0
+	li	a5, 9			/* Expected exception: S-mode ECALL. */
+	li	t0, 3 << 11
+	csrc	mstatus, t0
+	li	t0, 1 << 11
+	csrs	mstatus, t0		/* MPP=S */
+	lla	t0, \label
+	csrw	mepc, t0
+	mret
+	.endm
+
+	/* Each alias must be 64-bit in M-mode, even while SXLEN is 32. */
+	.macro	check_m64_alias csr
+	li	t0, 0x1234567811223344
+	csrw	CSR_SIREG, t0
+	csrr	t1, \csr
+	bne	t0, t1, fail
+	csrr	t1, CSR_SIREG
+	bne	t0, t1, fail
+
+	/* High-half aliases must still trap when the current XLEN is 64. */
+	li	a0, 0
+	li	a5, 2			/* Expect illegal instruction. */
+	li	s9, 0
+	csrr	t0, CSR_SIREG4
+	csrr	t0, CSR_SIREG5
+	li	t0, 2
+	bne	s9, t0, fail		/* Both accesses must have trapped. */
+	.endm
+
+	.macro	check_counter index, csr
+	li	s10, \index + 1		/* Counter-specific failure code. */
+	li	t0, 0x40 + \index
+	csrw	CSR_SISELECT, t0
+	check_m64_alias \csr
+	enter_s32 .Ls_counter\@
+.Ls_counter\@:
+#ifdef TEST_HIGH_HALF
+	csrr	t0, CSR_SIREG4		/* sireg4: original high half */
+	load32 t1, 0x12345678
+	bne	t0, t1, .Ls_fail\@
+	load32 t0, 0x07654321
+	csrw	CSR_SIREG4, t0
+	csrr	t1, CSR_SIREG4
+	bne	t0, t1, .Ls_fail\@
+	/* A high-half write must preserve the low half. */
+	csrr	t0, CSR_SIREG
+	load32 t1, 0x11223344
+#else
+	csrr	t0, CSR_SIREG		/* sireg: original low half */
+	load32 t1, 0x11223344
+	bne	t0, t1, .Ls_fail\@
+	load32 t0, 0x55667788
+	csrw	CSR_SIREG, t0
+	csrr	t1, CSR_SIREG
+#endif
+	bne	t0, t1, .Ls_fail\@
+	li	a0, 0
+	j	.Ls_done\@
+.Ls_fail\@:
+	mv	a0, s10
+.Ls_done\@:
+	ecall
+
+	/* Read the complete machine counter to check the unwritten half. */
+	csrr	t0, \csr
+#ifdef TEST_HIGH_HALF
+	li	t1, 0x0765432111223344
+#else
+	li	t1, 0x1234567855667788
+#endif
+	bne	t0, t1, fail
+	.endm
+
+	.macro	check_config index, csr, old_low, new_low
+	li	s10, \index + 1
+	li	t0, 0x40 + \index
+	csrw	CSR_SISELECT, t0
+	li	t0, 0x6000000000000000 | \old_low /* MINH | SINH */
+	csrw	\csr, t0
+	enter_s32 .Ls_config\@
+.Ls_config\@:
+	csrr	t0, CSR_SIREG2		/* sireg2: config/selector low half */
+	li	t1, \old_low
+	bne	t0, t1, .Lcfg_fail\@
+	li	t0, \new_low
+	csrw	CSR_SIREG2, t0
+	csrr	t1, CSR_SIREG2
+	bne	t0, t1, .Lcfg_fail\@
+
+	/* Low-half writes preserve the high half; MINH reads as zero. */
+	csrr	t0, CSR_SIREG5
+	li	t1, 0x20000000		/* SINH, without MINH */
+	bne	t0, t1, .Lcfg_fail\@
+	li	t0, 0x10000000		/* Replace SINH with UINH. */
+	csrw	CSR_SIREG5, t0
+	csrr	t1, CSR_SIREG5
+	bne	t0, t1, .Lcfg_fail\@
+	csrr	t0, CSR_SIREG2
+	li	t1, \new_low
+	bne	t0, t1, .Lcfg_fail\@
+	li	a0, 0
+	j	.Lcfg_done\@
+.Lcfg_fail\@:
+	mv	a0, s10
+.Lcfg_done\@:
+	ecall
+
+	/* Both the low half and the machine-only MINH bit must be retained. */
+	csrr	t0, \csr
+	li	t1, 0x5000000000000000 | \new_low /* MINH | UINH */
+	bne	t0, t1, fail
+	.endm
+
+	.text
+	.global	_start
+_start:
+	li	s10, 31			/* Setup failure. */
+	lla	t0, trap
+	csrw	mtvec, t0
+	csrw	medeleg, zero
+	csrw	mie, zero
+	li	t0, -1
+	/* Freeze counters for exact comparisons. */
+	csrw	mcountinhibit, t0
+	csrw	mcounteren, t0
+	csrw	pmpaddr0, t0
+	li	t0, 0x1f		/* Allow S-mode access to RAM. */
+	csrw	pmpcfg0, t0
+	li	t0, 1 << 60
+	csrw	CSR_MENVCFG, t0		/* CDE */
+	/* mstateen0: allow indirect CSRs. */
+	csrw	CSR_MSTATEEN0, t0
+
+	/* Select RV32 for lower privilege modes; M-mode remains RV64. */
+	csrr	t0, mstatus
+	li	t1, (3 << 34) | (3 << 32)
+	not	t1, t1
+	and	t0, t0, t1
+	li	t1, (1 << 34) | (1 << 32)
+	or	t0, t0, t1
+	csrw	mstatus, t0
+	csrr	t0, mstatus
+	srli	t0, t0, 34
+	andi	t0, t0, 3
+	li	t1, 1
+	bne	t0, t1, fail
+
+#ifdef TEST_CONFIG
+	check_config 0, CSR_MCYCLECFG, 0, 0
+	check_config 2, CSR_MINSTRETCFG, 0, 0
+	/* mhpmevent3: cycles -> instructions */
+	check_config 3, mhpmevent3, 1, 2
+#else
+	li	t0, 1
+	csrw	mhpmevent3, t0		/* HPM3 counts cycles. */
+	li	t0, 2
+	csrw	mhpmevent4, t0		/* HPM4 counts instructions. */
+	/* HPM5 has no selected event. */
+	csrw	mhpmevent5, zero
+	check_counter 0, mcycle
+	check_counter 2, minstret
+	check_counter 3, mhpmcounter3
+	check_counter 4, mhpmcounter4
+	check_counter 5, mhpmcounter5
+#endif
+	li	a0, 0
+	j	exit
+
+	.balign	4
+trap:
+	csrr	t0, mcause
+	bne	t0, a5, fail
+	bnez	a0, exit
+	addi	s9, s9, 1
+	csrr	t0, mepc
+	/* Zero-extend the RV32 trap PC for physical addressing in M-mode. */
+	slli	t0, t0, 32
+	srli	t0, t0, 32
+	addi	t0, t0, 4
+	csrw	mepc, t0
+	li	t0, 3 << 11
+	csrs	mstatus, t0		/* Resume in M-mode. */
+	mret
+
+fail:
+	mv	a0, s10
+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 */
+	.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 5d91381c62d77ae7e37c129112d6367f692df660..cfb868c45704e210e8fe951be0cbb752b589d51d 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -61,6 +61,37 @@ tests += {
   }
 }
 
+# Exercise RV32 CSRs with the RV64 emulator's 64-bit target_ulong.
+tests += {
+  '../riscv32/smcdeleg-rv32.S': {
+    'cflags': cflags + ['-march=rv32im_zicsr', '-mabi=ilp32'],
+    'qemu_args': ['-cpu', 'rv32,smcdeleg=true,ssccfg=true,sscofpmf=true', qemu_args],
+  },
+}
+
+tests += {
+  'smcdeleg-sxl32.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
+tests += {
+  'smcdeleg-sxl32.S': {
+    'exe_name': 'smcdeleg-sxl32-high',
+    'cflags': cflags + ['-DTEST_HIGH_HALF'],
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
+tests += {
+  'smcdeleg-sxl32.S': {
+    'exe_name': 'smcdeleg-sxl32-cfg',
+    'cflags': cflags + ['-DTEST_CONFIG'],
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
 tests += {
   'sscofpmf-overflow.S': {
     'cflags': cflags,

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 07/14] target/riscv: Preserve fixed counters across PMU state changes
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (5 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 06/14] target/riscv: Fix RV32 accesses to delegated PMU registers TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 08/14] target/riscv: Require Sscofpmf for non-fixed event overflow TANG Tiancheng
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

mcycle/minstret and HPM cycle/instruction counters use a stored value plus
the increment since a source baseline. Changing selectors, filters or
inhibit bits before accounting for the old settings can lose counts or
add inhibited time.

Take one snapshot, add the increments allowed by the old settings, apply
the write and establish the new baseline from that snapshot. Share this
sequence between direct and indirect CSR accesses.

Merge selector bits after accounting so low-half writes preserve OF set
by a pending wrap; explicit high-half or RV64 writes can still clear it.
Preserve full-width arithmetic for RV32 accesses and keep source baselines
independent of written counter bits. Delegated writes must preserve
machine MINH.

Test filter changes, RV32 counter halves and OF across partial selector
writes.

Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/cpu.h                      |   8 +-
 target/riscv/tcg/csr.c                  | 243 +++++--------------------
 target/riscv/tcg/pmu.c                  | 309 +++++++++++++++++++++++++++-----
 target/riscv/tcg/pmu.h                  |  18 +-
 tests/tcg/riscv32/pmu-fixed-rv32.S      |  90 ++++++++++
 tests/tcg/riscv32/sscofpmf-event-rv32.S |  99 ++++++++++
 tests/tcg/riscv32/system/meson.build    |  14 ++
 tests/tcg/riscv64/pmu-cycle-controls.S  |  80 +++++++++
 tests/tcg/riscv64/system/meson.build    |   7 +
 9 files changed, 622 insertions(+), 246 deletions(-)

diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index c2138dbd4ba312a5cb17f0916bce64d6faad38a9..f7b1bfc9cf5069125bc22dc2674d8e67431c5970 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -240,6 +240,12 @@ typedef struct PMUCTRState {
     uint64_t irq_overflow_left;
 } PMUCTRState;
 
+typedef enum {
+    RISCV_PMU_FIXED_DOMAIN_CYCLE,
+    RISCV_PMU_FIXED_DOMAIN_INSTRET,
+    RISCV_PMU_FIXED_DOMAIN_COUNT,
+} RISCVPMUFixedDomain;
+
 typedef struct PMUFixedCtrState {
     /* Track cycle and icount for each privilege mode */
     uint64_t counter[4];
@@ -465,7 +471,7 @@ struct CPUArchState {
      */
     uint64_t mhpmevent_val[RV_MAX_MHPMEVENTS];
 
-    PMUFixedCtrState pmu_fixed_ctrs[2];
+    PMUFixedCtrState pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_COUNT];
 
     uint64_t sscratch;
     uint64_t mscratch;
diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
index ec6cc6081cb1aa43dc8ee0755b1a4eba1b63350d..ac073e712a4ee3ddc5c97d40c6ac33c1539fc6e5 100644
--- a/target/riscv/tcg/csr.c
+++ b/target/riscv/tcg/csr.c
@@ -1110,9 +1110,10 @@ static RISCVException write_mcyclecfg(CPURISCVState *env, int csrno,
                                       target_ulong val, uintptr_t ra)
 {
     uint64_t inh_avail_mask;
+    uint64_t value;
 
     if (riscv_cpu_mxl(env) == MXL_RV32) {
-        env->mcyclecfg = deposit64(env->mcyclecfg, 0, 32, val);
+        value = deposit64(env->mcyclecfg, 0, 32, val);
     } else {
         /* Set xINH fields if priv mode supported */
         inh_avail_mask = ~MHPMEVENT_FILTER_MASK | MCYCLECFG_BIT_MINH;
@@ -1122,8 +1123,9 @@ static RISCVException write_mcyclecfg(CPURISCVState *env, int csrno,
                            riscv_has_ext(env, RVU)) ? MCYCLECFG_BIT_VUINH : 0;
         inh_avail_mask |= (riscv_has_ext(env, RVH) &&
                            riscv_has_ext(env, RVS)) ? MCYCLECFG_BIT_VSINH : 0;
-        env->mcyclecfg = val & inh_avail_mask;
+        value = val & inh_avail_mask;
     }
+    riscv_pmu_write_ctr_cfg(env, 0, value);
 
     return RISCV_EXCP_NONE;
 }
@@ -1149,7 +1151,9 @@ static RISCVException write_mcyclecfgh(CPURISCVState *env, int csrno,
     inh_avail_mask |= (riscv_has_ext(env, RVH) &&
                        riscv_has_ext(env, RVS)) ? MCYCLECFGH_BIT_VSINH : 0;
 
-    env->mcyclecfg = deposit64(env->mcyclecfg, 32, 32, val & inh_avail_mask);
+    riscv_pmu_write_ctr_cfg(env, 0,
+                            deposit64(env->mcyclecfg, 32, 32,
+                                      val & inh_avail_mask));
     return RISCV_EXCP_NONE;
 }
 
@@ -1165,9 +1169,10 @@ static RISCVException write_minstretcfg(CPURISCVState *env, int csrno,
                                         target_ulong val, uintptr_t ra)
 {
     uint64_t inh_avail_mask;
+    uint64_t value;
 
     if (riscv_cpu_mxl(env) == MXL_RV32) {
-        env->minstretcfg = deposit64(env->minstretcfg, 0, 32, val);
+        value = deposit64(env->minstretcfg, 0, 32, val);
     } else {
         inh_avail_mask = ~MHPMEVENT_FILTER_MASK | MINSTRETCFG_BIT_MINH;
         inh_avail_mask |= riscv_has_ext(env, RVU) ? MINSTRETCFG_BIT_UINH : 0;
@@ -1176,8 +1181,9 @@ static RISCVException write_minstretcfg(CPURISCVState *env, int csrno,
                            riscv_has_ext(env, RVU)) ? MINSTRETCFG_BIT_VUINH : 0;
         inh_avail_mask |= (riscv_has_ext(env, RVH) &&
                            riscv_has_ext(env, RVS)) ? MINSTRETCFG_BIT_VSINH : 0;
-        env->minstretcfg = val & inh_avail_mask;
+        value = val & inh_avail_mask;
     }
+    riscv_pmu_write_ctr_cfg(env, 2, value);
     return RISCV_EXCP_NONE;
 }
 
@@ -1201,8 +1207,9 @@ static RISCVException write_minstretcfgh(CPURISCVState *env, int csrno,
     inh_avail_mask |= (riscv_has_ext(env, RVH) &&
                        riscv_has_ext(env, RVS)) ? MINSTRETCFGH_BIT_VSINH : 0;
 
-    env->minstretcfg = deposit64(env->minstretcfg, 32, 32,
-                                 val & inh_avail_mask);
+    riscv_pmu_write_ctr_cfg(env, 2,
+                            deposit64(env->minstretcfg, 32, 32,
+                                      val & inh_avail_mask));
     return RISCV_EXCP_NONE;
 }
 
@@ -1217,50 +1224,17 @@ static RISCVException read_mhpmevent(CPURISCVState *env, int csrno,
     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_rebuild_event_map(env);
-
-    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 ctr_idx = csrno - CSR_MCOUNTINHIBIT;
     uint64_t mhpmevt_val;
     uint64_t inh_avail_mask;
+    uint64_t wr_mask = UINT64_MAX;
 
     if (riscv_cpu_mxl(env) == MXL_RV32) {
-        mhpmevt_val = deposit64(env->mhpmevent_val[ctr_idx], 0, 32, val);
+        mhpmevt_val = val;
+        wr_mask = UINT32_MAX;
     } else {
         inh_avail_mask = ~MHPMEVENT_FILTER_MASK | MHPMEVENT_BIT_MINH;
         inh_avail_mask |= riscv_has_ext(env, RVU) ? MHPMEVENT_BIT_UINH : 0;
@@ -1272,7 +1246,7 @@ static RISCVException write_mhpmevent(CPURISCVState *env, int csrno,
         mhpmevt_val = val & inh_avail_mask;
     }
 
-    riscv_pmu_write_mhpmevent(env, ctr_idx, mhpmevt_val);
+    riscv_pmu_write_event(env, ctr_idx, mhpmevt_val, wr_mask);
 
     return RISCV_EXCP_NONE;
 }
@@ -1301,9 +1275,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;
 
-    riscv_pmu_write_mhpmevent(env, ctr_idx,
-                              deposit64(env->mhpmevent_val[ctr_idx], 32, 32,
-                                        val & inh_avail_mask));
+    riscv_pmu_write_event(env, ctr_idx,
+                          (uint64_t)(val & inh_avail_mask) << 32,
+                          MAKE_64BIT_MASK(32, 32));
 
     return RISCV_EXCP_NONE;
 }
@@ -1311,106 +1285,10 @@ static RISCVException write_mhpmeventh(CPURISCVState *env, int csrno,
 static uint64_t riscv_pmu_ctr_get_fixed_counters_val(CPURISCVState *env,
                                                      int counter_idx)
 {
-    int inst = riscv_pmu_ctr_monitor_instructions(env, counter_idx);
-    uint64_t *counter_arr_virt = env->pmu_fixed_ctrs[inst].counter_virt;
-    uint64_t *counter_arr = env->pmu_fixed_ctrs[inst].counter;
-    uint64_t curr_val = 0;
-    uint64_t cfg_val = 0;
-
-    if (counter_idx == 0) {
-        cfg_val = env->mcyclecfg;
-    } else if (counter_idx == 2) {
-        cfg_val = env->minstretcfg;
-    } else {
-        cfg_val = env->mhpmevent_val[counter_idx];
-        cfg_val &= MHPMEVENT_FILTER_MASK;
-    }
-
-    if (!cfg_val) {
-        return riscv_pmu_read_fixed_source(env, inst);
-    }
-
-    /* Update counter before reading. */
-    riscv_pmu_update_fixed_ctrs(env, env->priv, env->virt_enabled);
-
-    if (!(cfg_val & MCYCLECFG_BIT_MINH)) {
-        curr_val += counter_arr[PRV_M];
-    }
-
-    if (!(cfg_val & MCYCLECFG_BIT_SINH)) {
-        curr_val += counter_arr[PRV_S];
-    }
+    RISCVPMUFixedSnapshot snapshot;
 
-    if (!(cfg_val & MCYCLECFG_BIT_UINH)) {
-        curr_val += counter_arr[PRV_U];
-    }
-
-    if (!(cfg_val & MCYCLECFG_BIT_VSINH)) {
-        curr_val += counter_arr_virt[PRV_S];
-    }
-
-    if (!(cfg_val & MCYCLECFG_BIT_VUINH)) {
-        curr_val += counter_arr_virt[PRV_U];
-    }
-
-    return curr_val;
-}
-
-static RISCVException riscv_pmu_write_ctr(CPURISCVState *env, target_ulong val,
-                                          uint32_t ctr_idx, RISCVMXL xl)
-{
-    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
-    bool rv32 = xl == MXL_RV32;
-    int deposit_size = rv32 ? 32 : 64;
-    uint64_t ctr;
-
-    if (!get_field(env->mcountinhibit, BIT(ctr_idx)) &&
-        (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
-         riscv_pmu_ctr_monitor_instructions(env, ctr_idx))) {
-        ctr = riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx);
-        counter->mhpmcounter_val += ctr - counter->mhpmcounter_prev;
-        counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
-                                             0, deposit_size, val);
-        counter->mhpmcounter_prev = ctr;
-        if (ctr_idx > 2) {
-            riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
-        }
-     } else {
-        counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
-                                             0, deposit_size, val);
-        /* Other counters can keep incrementing from the given value */
-        counter->mhpmcounter_prev = deposit64(counter->mhpmcounter_prev,
-                                              0, deposit_size, val);
-    }
-
-    return RISCV_EXCP_NONE;
-}
-
-static RISCVException riscv_pmu_write_ctrh(CPURISCVState *env, target_ulong val,
-                                          uint32_t ctr_idx)
-{
-    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
-    uint64_t ctr;
-
-    if (!get_field(env->mcountinhibit, BIT(ctr_idx)) &&
-        (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
-         riscv_pmu_ctr_monitor_instructions(env, ctr_idx))) {
-        ctr = riscv_pmu_ctr_get_fixed_counters_val(env, ctr_idx);
-        counter->mhpmcounter_val += ctr - counter->mhpmcounter_prev;
-        counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
-                                             32, 32, val);
-        counter->mhpmcounter_prev = ctr;
-        if (ctr_idx > 2) {
-            riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
-        }
-    } else {
-        counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
-                                             32, 32, val);
-        counter->mhpmcounter_prev = deposit64(counter->mhpmcounter_prev,
-                                              32, 32, val);
-    }
-
-    return RISCV_EXCP_NONE;
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    return riscv_pmu_ctr_get_fixed_value(env, counter_idx, &snapshot);
 }
 
 static RISCVException write_mhpmcounter(CPURISCVState *env, int csrno,
@@ -1418,7 +1296,8 @@ static RISCVException write_mhpmcounter(CPURISCVState *env, int csrno,
 {
     int ctr_idx = csrno - CSR_MCYCLE;
 
-    return riscv_pmu_write_ctr(env, val, ctr_idx, riscv_cpu_mxl(env));
+    riscv_pmu_write_counter(env, ctr_idx, val, false, riscv_cpu_mxl(env));
+    return RISCV_EXCP_NONE;
 }
 
 static RISCVException write_mhpmcounterh(CPURISCVState *env, int csrno,
@@ -1426,7 +1305,8 @@ static RISCVException write_mhpmcounterh(CPURISCVState *env, int csrno,
 {
     int ctr_idx = csrno - CSR_MCYCLEH;
 
-    return riscv_pmu_write_ctrh(env, val, ctr_idx);
+    riscv_pmu_write_counter(env, ctr_idx, val, true, riscv_cpu_mxl(env));
+    return RISCV_EXCP_NONE;
 }
 
 RISCVException riscv_pmu_read_ctr(CPURISCVState *env, target_ulong *val,
@@ -1515,7 +1395,7 @@ static int rmw_cd_mhpmcounter(CPURISCVState *env, int ctr_idx,
     if (!wr_mask && val) {
         riscv_pmu_read_ctr(env, val, false, ctr_idx, env->xl);
     } else if (wr_mask) {
-        riscv_pmu_write_ctr(env, new_val, ctr_idx, env->xl);
+        riscv_pmu_write_counter(env, ctr_idx, new_val, false, env->xl);
     } else {
         return -EINVAL;
     }
@@ -1534,7 +1414,7 @@ static int rmw_cd_mhpmcounterh(CPURISCVState *env, int ctr_idx,
     if (!wr_mask && val) {
         riscv_pmu_read_ctr(env, val, true, ctr_idx, env->xl);
     } else if (wr_mask) {
-        riscv_pmu_write_ctrh(env, new_val, ctr_idx);
+        riscv_pmu_write_counter(env, ctr_idx, new_val, true, env->xl);
     } else {
         return -EINVAL;
     }
@@ -1560,9 +1440,7 @@ static int rmw_cd_mhpmevent(CPURISCVState *env, int ctr_idx,
         }
     } else if (wr_mask) {
         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);
-        riscv_pmu_write_mhpmevent(env, ctr_idx, mhpmevt_val);
+        riscv_pmu_write_event(env, ctr_idx, new_val, wr_mask);
     } else {
         return -EINVAL;
     }
@@ -1588,9 +1466,8 @@ static int rmw_cd_mhpmeventh(CPURISCVState *env, int ctr_idx,
         }
     } else if (wr_mask) {
         wr_mask &= ~MHPMEVENTH_BIT_MINH;
-        mhpmevth_val = (new_val & wr_mask) | (mhpmevth_val & ~wr_mask);
-        mhpmevt_val = deposit64(mhpmevt_val, 32, 32, mhpmevth_val);
-        riscv_pmu_write_mhpmevent(env, ctr_idx, mhpmevt_val);
+        riscv_pmu_write_event(env, ctr_idx, (uint64_t)new_val << 32,
+                              (uint64_t)wr_mask << 32);
     } else {
         return -EINVAL;
     }
@@ -1609,7 +1486,9 @@ static int rmw_cd_ctr_cfg(CPURISCVState *env, int cfg_index, target_ulong *val,
     case 0:             /* CYCLECFG */
         if (wr_mask) {
             wr_mask &= ~MCYCLECFG_BIT_MINH;
-            env->mcyclecfg = (new_val & wr_mask) | (env->mcyclecfg & ~wr_mask);
+            riscv_pmu_write_ctr_cfg(env, 0,
+                                    (new_val & wr_mask) |
+                                    (env->mcyclecfg & ~wr_mask));
         } else {
             *val = env->mcyclecfg & ~MCYCLECFG_BIT_MINH;
         }
@@ -1617,8 +1496,9 @@ static int rmw_cd_ctr_cfg(CPURISCVState *env, int cfg_index, target_ulong *val,
     case 2:             /* INSTRETCFG */
         if (wr_mask) {
             wr_mask &= ~MINSTRETCFG_BIT_MINH;
-            env->minstretcfg = (new_val & wr_mask) |
-                               (env->minstretcfg & ~wr_mask);
+            riscv_pmu_write_ctr_cfg(env, 2,
+                                    (new_val & wr_mask) |
+                                    (env->minstretcfg & ~wr_mask));
         } else {
             *val = env->minstretcfg & ~MINSTRETCFG_BIT_MINH;
         }
@@ -1640,7 +1520,8 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
         if (wr_mask) {
             wr_mask &= ~MCYCLECFGH_BIT_MINH;
             cfgh = (new_val & wr_mask) | (cfgh & ~wr_mask);
-            env->mcyclecfg = deposit64(env->mcyclecfg, 32, 32, cfgh);
+            riscv_pmu_write_ctr_cfg(env, 0,
+                                    deposit64(env->mcyclecfg, 32, 32, cfgh));
         } else {
             *val = cfgh & ~MCYCLECFGH_BIT_MINH;
         }
@@ -1650,7 +1531,8 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
         if (wr_mask) {
             wr_mask &= ~MINSTRETCFGH_BIT_MINH;
             cfgh = (new_val & wr_mask) | (cfgh & ~wr_mask);
-            env->minstretcfg = deposit64(env->minstretcfg, 32, 32, cfgh);
+            riscv_pmu_write_ctr_cfg(env, 2,
+                                    deposit64(env->minstretcfg, 32, 32, cfgh));
         } else {
             *val = cfgh & ~MINSTRETCFGH_BIT_MINH;
         }
@@ -3089,44 +2971,7 @@ static RISCVException read_mcountinhibit(CPURISCVState *env, int csrno,
 static RISCVException write_mcountinhibit(CPURISCVState *env, int csrno,
                                           target_ulong val, uintptr_t ra)
 {
-    int cidx;
-    PMUCTRState *counter;
-    RISCVCPU *cpu = env_archcpu(env);
-    uint32_t present_ctrs = cpu->pmu_avail_ctrs | COUNTEREN_CY | COUNTEREN_IR;
-    target_ulong updated_ctrs = (env->mcountinhibit ^ val) & present_ctrs;
-    uint64_t mhpmctr_val, prev_count, curr_count;
-
-    /* WARL register - disable unavailable counters; TM bit is always 0 */
-    env->mcountinhibit = val & present_ctrs;
-
-    /* Check if any other counter is also monitoring cycles/instructions */
-    for (cidx = 0; cidx < RV_MAX_MHPMCOUNTERS; cidx++) {
-        if (!(updated_ctrs & BIT(cidx)) ||
-            (!riscv_pmu_ctr_monitor_cycles(env, cidx) &&
-            !riscv_pmu_ctr_monitor_instructions(env, cidx))) {
-            continue;
-        }
-
-        counter = &env->pmu_ctrs[cidx];
-
-        if (!get_field(env->mcountinhibit, BIT(cidx))) {
-            counter->mhpmcounter_prev = riscv_pmu_ctr_get_fixed_counters_val(env, cidx);
-
-            if (cidx > 2) {
-                riscv_pmu_setup_timer(env, counter->mhpmcounter_val, cidx);
-            }
-        } else {
-            curr_count = riscv_pmu_ctr_get_fixed_counters_val(env, cidx);
-
-            mhpmctr_val = counter->mhpmcounter_val;
-            prev_count = counter->mhpmcounter_prev;
-
-            /* Adjust the counter for later reads. */
-            mhpmctr_val = curr_count - prev_count + mhpmctr_val;
-            counter->mhpmcounter_val = mhpmctr_val;
-        }
-    }
-
+    riscv_pmu_write_inhibit(env, val);
     return RISCV_EXCP_NONE;
 }
 
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 54fff2ba49c1034d5fa6db1c7b19ff59003cd1e1..16942e53489cd5a2d2dd055fdffef07d04d59465 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -85,15 +85,27 @@ static bool riscv_pmu_counter_filtered(CPURISCVState *env, uint64_t cfg)
  * 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)
+static uint64_t riscv_pmu_read_fixed_source(CPURISCVState *env,
+                                            RISCVPMUFixedDomain domain)
 {
-    if (instret && icount_enabled()) {
+    if (domain == RISCV_PMU_FIXED_DOMAIN_INSTRET && icount_enabled()) {
         return icount_get_raw();
     }
 
+    g_assert(domain == RISCV_PMU_FIXED_DOMAIN_CYCLE ||
+             domain == RISCV_PMU_FIXED_DOMAIN_INSTRET);
     return cpus_get_elapsed_ticks();
 }
 
+void riscv_pmu_take_fixed_snapshot(CPURISCVState *env,
+                                   RISCVPMUFixedSnapshot *snapshot)
+{
+    snapshot->cycle =
+        riscv_pmu_read_fixed_source(env, RISCV_PMU_FIXED_DOMAIN_CYCLE);
+    snapshot->instret =
+        riscv_pmu_read_fixed_source(env, RISCV_PMU_FIXED_DOMAIN_INSTRET);
+}
+
 /*
  * Information needed to update counters:
  *  new_priv, new_virt: To correctly save starting snapshot for the newly
@@ -106,82 +118,289 @@ uint64_t riscv_pmu_read_fixed_source(CPURISCVState *env, bool instret)
  *  env->priv and env->virt_enabled contain old priv and old virt and
  *  new priv and new virt values are passed in as arguments.
  */
-static void riscv_pmu_icount_update_priv(CPURISCVState *env,
-                                         privilege_mode_t newpriv,
-                                         bool new_virt)
+static void riscv_pmu_fixed_update_priv(CPURISCVState *env,
+                                        privilege_mode_t newpriv,
+                                        bool new_virt,
+                                        RISCVPMUFixedDomain domain,
+                                        uint64_t source)
 {
+    PMUFixedCtrState *fixed = &env->pmu_fixed_ctrs[domain];
     uint64_t *snapshot_prev, *snapshot_new;
-    uint64_t current_icount;
     uint64_t *counter_arr;
     uint64_t delta;
 
-    current_icount = riscv_pmu_read_fixed_source(env, true);
-
     if (env->virt_enabled) {
         g_assert(env->priv <= PRV_S);
-        counter_arr = env->pmu_fixed_ctrs[1].counter_virt;
-        snapshot_prev = env->pmu_fixed_ctrs[1].counter_virt_prev;
+        counter_arr = fixed->counter_virt;
+        snapshot_prev = fixed->counter_virt_prev;
     } else {
-        counter_arr = env->pmu_fixed_ctrs[1].counter;
-        snapshot_prev = env->pmu_fixed_ctrs[1].counter_prev;
+        counter_arr = fixed->counter;
+        snapshot_prev = fixed->counter_prev;
     }
 
     if (new_virt) {
         g_assert(newpriv <= PRV_S);
-        snapshot_new = env->pmu_fixed_ctrs[1].counter_virt_prev;
+        snapshot_new = fixed->counter_virt_prev;
     } else {
-        snapshot_new = env->pmu_fixed_ctrs[1].counter_prev;
+        snapshot_new = fixed->counter_prev;
     }
 
-     /*
-      * new_priv can be same as env->priv. So we need to calculate
-      * delta first before updating snapshot_new[new_priv].
-      */
-    delta = current_icount - snapshot_prev[env->priv];
-    snapshot_new[newpriv] = current_icount;
+    /*
+     * new_priv can be same as env->priv. So we need to calculate
+     * delta first before updating snapshot_new[new_priv].
+     */
+    delta = source - snapshot_prev[env->priv];
+    snapshot_new[newpriv] = source;
 
     counter_arr[env->priv] += delta;
 }
 
-static void riscv_pmu_cycle_update_priv(CPURISCVState *env,
-                                        privilege_mode_t newpriv,
-                                        bool new_virt)
+static void
+riscv_pmu_update_fixed_ctrs_snapshot(CPURISCVState *env,
+                                     privilege_mode_t newpriv, bool new_virt,
+                                     const RISCVPMUFixedSnapshot *snapshot)
 {
-    uint64_t *snapshot_prev, *snapshot_new;
-    uint64_t current_ticks;
+    riscv_pmu_fixed_update_priv(env, newpriv, new_virt,
+                                RISCV_PMU_FIXED_DOMAIN_CYCLE,
+                                snapshot->cycle);
+    riscv_pmu_fixed_update_priv(env, newpriv, new_virt,
+                                RISCV_PMU_FIXED_DOMAIN_INSTRET,
+                                snapshot->instret);
+}
+
+void riscv_pmu_update_fixed_ctrs(CPURISCVState *env,
+                                 privilege_mode_t newpriv,
+                                 bool new_virt)
+{
+    RISCVPMUFixedSnapshot snapshot;
+
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    riscv_pmu_update_fixed_ctrs_snapshot(env, newpriv, new_virt, &snapshot);
+}
+
+uint64_t
+riscv_pmu_ctr_get_fixed_value(CPURISCVState *env, uint32_t ctr_idx,
+                              const RISCVPMUFixedSnapshot *snapshot)
+{
+    RISCVPMUFixedDomain domain;
+    PMUFixedCtrState *fixed;
+    uint64_t *counter_arr_virt;
     uint64_t *counter_arr;
-    uint64_t delta;
+    uint64_t cfg;
+    uint64_t value = 0;
 
-    current_ticks = riscv_pmu_read_fixed_source(env, false);
+    if (riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
+        domain = RISCV_PMU_FIXED_DOMAIN_INSTRET;
+    } else {
+        domain = RISCV_PMU_FIXED_DOMAIN_CYCLE;
+    }
 
-    if (env->virt_enabled) {
-        g_assert(env->priv <= PRV_S);
-        counter_arr = env->pmu_fixed_ctrs[0].counter_virt;
-        snapshot_prev = env->pmu_fixed_ctrs[0].counter_virt_prev;
+    fixed = &env->pmu_fixed_ctrs[domain];
+    counter_arr_virt = fixed->counter_virt;
+    counter_arr = fixed->counter;
+
+    if (ctr_idx == 0) {
+        cfg = env->mcyclecfg;
+    } else if (ctr_idx == 2) {
+        cfg = env->minstretcfg;
     } else {
-        counter_arr = env->pmu_fixed_ctrs[0].counter;
-        snapshot_prev = env->pmu_fixed_ctrs[0].counter_prev;
+        cfg = env->mhpmevent_val[ctr_idx] & MHPMEVENT_FILTER_MASK;
     }
 
-    if (new_virt) {
-        g_assert(newpriv <= PRV_S);
-        snapshot_new = env->pmu_fixed_ctrs[0].counter_virt_prev;
+    if (!cfg) {
+        return domain == RISCV_PMU_FIXED_DOMAIN_INSTRET ?
+               snapshot->instret : snapshot->cycle;
+    }
+
+    riscv_pmu_update_fixed_ctrs_snapshot(env, env->priv, env->virt_enabled,
+                                         snapshot);
+
+    if (!(cfg & MCYCLECFG_BIT_MINH)) {
+        value += counter_arr[PRV_M];
+    }
+    if (!(cfg & MCYCLECFG_BIT_SINH)) {
+        value += counter_arr[PRV_S];
+    }
+    if (!(cfg & MCYCLECFG_BIT_UINH)) {
+        value += counter_arr[PRV_U];
+    }
+    if (!(cfg & MCYCLECFG_BIT_VSINH)) {
+        value += counter_arr_virt[PRV_S];
+    }
+    if (!(cfg & MCYCLECFG_BIT_VUINH)) {
+        value += counter_arr_virt[PRV_U];
+    }
+
+    return value;
+}
+
+static bool riscv_pmu_fixed_ctr_selected(CPURISCVState *env,
+                                         uint32_t ctr_idx)
+{
+    return riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
+           riscv_pmu_ctr_monitor_instructions(env, ctr_idx);
+}
+
+static bool riscv_pmu_fixed_ctr_enabled(CPURISCVState *env,
+                                        uint32_t ctr_idx)
+{
+    return !(env->mcountinhibit & BIT(ctr_idx)) &&
+           riscv_pmu_fixed_ctr_selected(env, ctr_idx);
+}
+
+static bool riscv_pmu_fixed_ctr_running(CPURISCVState *env,
+                                        uint32_t ctr_idx)
+{
+    return riscv_pmu_fixed_ctr_enabled(env, ctr_idx);
+}
+
+static void riscv_pmu_set_overflow(CPURISCVState *env, uint32_t ctr_idx)
+{
+    if (ctr_idx < 3 || !riscv_cpu_cfg(env)->ext_sscofpmf ||
+        (env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF)) {
+        return;
+    }
+
+    env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
+    riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
+}
+
+/*
+ * Accumulate the delta from mhpmcounter_prev to the fixed source snapshot,
+ * then align mhpmcounter_prev with that snapshot.
+ */
+static void
+riscv_pmu_accumulate_fixed_delta(CPURISCVState *env, uint32_t ctr_idx,
+                                 const RISCVPMUFixedSnapshot *snapshot)
+{
+    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
+    uint64_t source, delta, value;
+
+    g_assert(riscv_pmu_fixed_ctr_selected(env, ctr_idx));
+
+    source = riscv_pmu_ctr_get_fixed_value(env, ctr_idx, snapshot);
+    delta = source - counter->mhpmcounter_prev;
+    value = counter->mhpmcounter_val;
+
+    if (delta > UINT64_MAX - value) {
+        riscv_pmu_set_overflow(env, ctr_idx);
+    }
+
+    counter->mhpmcounter_val = value + delta;
+    counter->mhpmcounter_prev = source;
+}
+
+static void
+riscv_pmu_set_fixed_baseline(CPURISCVState *env, uint32_t ctr_idx,
+                             const RISCVPMUFixedSnapshot *snapshot)
+{
+    g_assert(riscv_pmu_fixed_ctr_selected(env, ctr_idx));
+    env->pmu_ctrs[ctr_idx].mhpmcounter_prev =
+        riscv_pmu_ctr_get_fixed_value(env, ctr_idx, snapshot);
+}
+
+void riscv_pmu_write_ctr_cfg(CPURISCVState *env, uint32_t ctr_idx,
+                             uint64_t value)
+{
+    RISCVPMUFixedSnapshot snapshot;
+
+    g_assert(ctr_idx == 0 || ctr_idx == 2);
+
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+        riscv_pmu_accumulate_fixed_delta(env, ctr_idx, &snapshot);
+    }
+    if (ctr_idx == 0) {
+        env->mcyclecfg = value;
     } else {
-        snapshot_new = env->pmu_fixed_ctrs[0].counter_prev;
+        env->minstretcfg = value;
+    }
+    if (riscv_pmu_fixed_ctr_enabled(env, ctr_idx)) {
+        riscv_pmu_set_fixed_baseline(env, ctr_idx, &snapshot);
     }
+}
 
-    delta = current_ticks - snapshot_prev[env->priv];
-    snapshot_new[newpriv] = current_ticks;
+void riscv_pmu_write_event(CPURISCVState *env, uint32_t ctr_idx,
+                           uint64_t value, uint64_t wr_mask)
+{
+    RISCVPMUFixedSnapshot snapshot;
+    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
 
-    counter_arr[env->priv] += delta;
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+        riscv_pmu_accumulate_fixed_delta(env, ctr_idx, &snapshot);
+    }
+    /* Accumulating the old source can set OF outside the written bits. */
+    env->mhpmevent_val[ctr_idx] = (value & wr_mask) |
+                                (env->mhpmevent_val[ctr_idx] & ~wr_mask);
+    riscv_pmu_rebuild_event_map(env);
+    if (riscv_pmu_fixed_ctr_enabled(env, ctr_idx)) {
+        riscv_pmu_set_fixed_baseline(env, ctr_idx, &snapshot);
+    }
+
+    if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+        riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
+    }
 }
 
-void riscv_pmu_update_fixed_ctrs(CPURISCVState *env,
-                                 privilege_mode_t newpriv,
-                                 bool new_virt)
+void riscv_pmu_write_counter(CPURISCVState *env, uint32_t ctr_idx,
+                             target_ulong value, bool upper_half, RISCVMXL xl)
+{
+    RISCVPMUFixedSnapshot snapshot;
+    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
+    bool rv32 = xl == MXL_RV32;
+    bool running;
+    int start = upper_half ? 32 : 0;
+    int length = rv32 ? 32 : 64;
+
+    g_assert(rv32 || !upper_half);
+
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    running = riscv_pmu_fixed_ctr_running(env, ctr_idx);
+    if (running) {
+        riscv_pmu_accumulate_fixed_delta(env, ctr_idx, &snapshot);
+    }
+    counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
+                                         start, length, value);
+    /* mhpmcounter_prev tracks the source, not the written counter value. */
+    if (running && ctr_idx > 2) {
+        riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
+    }
+}
+
+void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t value)
 {
-    riscv_pmu_cycle_update_priv(env, newpriv, new_virt);
-    riscv_pmu_icount_update_priv(env, newpriv, new_virt);
+    RISCVCPU *cpu = env_archcpu(env);
+    RISCVPMUFixedSnapshot snapshot;
+    uint32_t present = cpu->pmu_avail_ctrs | COUNTEREN_CY | COUNTEREN_IR;
+    uint32_t old = env->mcountinhibit;
+    uint32_t changed = (old ^ value) & present;
+    uint32_t ctr_idx;
+
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    for (ctr_idx = 0; ctr_idx < RV_MAX_MHPMCOUNTERS; ctr_idx++) {
+        if ((changed & BIT(ctr_idx)) && !(old & BIT(ctr_idx)) &&
+            riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+            riscv_pmu_accumulate_fixed_delta(env, ctr_idx, &snapshot);
+        }
+    }
+
+    env->mcountinhibit = value & present;
+
+    for (ctr_idx = 0; ctr_idx < RV_MAX_MHPMCOUNTERS; ctr_idx++) {
+        if (!(changed & BIT(ctr_idx)) ||
+            (env->mcountinhibit & BIT(ctr_idx))) {
+            continue;
+        }
+
+        if (riscv_pmu_fixed_ctr_enabled(env, ctr_idx)) {
+            riscv_pmu_set_fixed_baseline(env, ctr_idx, &snapshot);
+        }
+        if (ctr_idx > 2 && riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+            riscv_pmu_setup_timer(env, env->pmu_ctrs[ctr_idx].mhpmcounter_val,
+                                  ctr_idx);
+        }
+    }
 }
 
 void riscv_pmu_decr_instret(CPURISCVState *env)
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index bf2e8373474d471d914f8801c55d2f6ffbb5cdd3..1494fbc21f53137a90c1338f6ca8e3c3e750276a 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -22,11 +22,27 @@
 #include "cpu.h"
 #include "qapi/error.h"
 
+typedef struct RISCVPMUFixedSnapshot {
+    uint64_t cycle;
+    uint64_t instret;
+} RISCVPMUFixedSnapshot;
+
 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_take_fixed_snapshot(CPURISCVState *env,
+                                   RISCVPMUFixedSnapshot *snapshot);
+uint64_t riscv_pmu_ctr_get_fixed_value(CPURISCVState *env,
+                                       uint32_t ctr_idx,
+                                       const RISCVPMUFixedSnapshot *snapshot);
+void riscv_pmu_write_ctr_cfg(CPURISCVState *env, uint32_t ctr_idx,
+                             uint64_t value);
+void riscv_pmu_write_event(CPURISCVState *env, uint32_t ctr_idx,
+                           uint64_t value, uint64_t wr_mask);
+void riscv_pmu_write_counter(CPURISCVState *env, uint32_t ctr_idx,
+                             target_ulong value, bool upper_half, RISCVMXL xl);
+void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t value);
 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/riscv32/pmu-fixed-rv32.S b/tests/tcg/riscv32/pmu-fixed-rv32.S
new file mode 100644
index 0000000000000000000000000000000000000000..85917fe0caa40621d873be2a70426552bde975c3
--- /dev/null
+++ b/tests/tcg/riscv32/pmu-fixed-rv32.S
@@ -0,0 +1,90 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global _start
+_start:
+	/*
+	 * Failure bits:
+	 * 0: selecting cycles changes the initialized high half
+	 * 1: a low-half write discards a carry into the visible high half
+	 * 2: the high-half read does not match the value just written
+	 */
+	li	t4, 0
+	csrw	mhpmevent3, zero
+	csrw	mhpmcounter3, zero
+	li	t0, 1
+	csrw	mhpmcounter3h, t0
+	li	t0, 1
+	csrw	mhpmevent3, t0		/* mhpmevent3: cycles */
+
+	/* Starting the counter must preserve its initialized high half. */
+	csrr	t0, hpmcounter3h
+	li	t1, 1
+	xor	t0, t0, t1
+	sltu	t0, zero, t0
+	or	t4, t4, t0
+
+	/*
+	 * Start 256 cycles below 2 << 32.  With -icount shift=0, the
+	 * following instructions carry into the visible high half.  A
+	 * low-half write must replace only bits 31:0 and preserve that carry.
+	 */
+	csrw	mhpmevent3, zero
+	li	t0, -256
+	csrw	mhpmcounter3, t0
+	li	t0, 1
+	csrw	mhpmcounter3h, t0
+	li	t0, 1
+	csrw	mhpmevent3, t0		/* mhpmevent3: cycles */
+	.rept	512
+	nop
+	.endr
+	li	t0, 0x1234
+	csrw	mhpmcounter3, t0
+	csrr	t0, hpmcounter3h
+	li	t1, 2
+	xor	t0, t0, t1
+	sltu	t0, zero, t0
+	slli	t0, t0, 1
+	or	t4, t4, t0
+
+	/*
+	 * Restart with a small low half so no carry can affect this check.
+	 * Writing 2 to the running counter's high half must read back as 2.
+	 */
+	csrw	mhpmevent3, zero
+	li	t0, 0x1234
+	csrw	mhpmcounter3, t0
+	li	t0, 1
+	csrw	mhpmcounter3h, t0
+	li	t0, 1
+	csrw	mhpmevent3, t0		/* mhpmevent3: cycles */
+	li	t0, 2
+	csrw	mhpmcounter3h, t0
+	csrr	t0, hpmcounter3h
+	li	t1, 2
+	xor	t0, t0, t1
+	sltu	t0, zero, t0
+	slli	t0, t0, 2
+	or	t4, t4, t0
+
+	la	a1, semiargs
+	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
+	sw	t0, 0(a1)
+	sw	t4, 4(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	8
diff --git a/tests/tcg/riscv32/sscofpmf-event-rv32.S b/tests/tcg/riscv32/sscofpmf-event-rv32.S
new file mode 100644
index 0000000000000000000000000000000000000000..0c769c5f6bea4350a197dd044e6959b28fc69700
--- /dev/null
+++ b/tests/tcg/riscv32/sscofpmf-event-rv32.S
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR numbers for older assemblers. */
+#define CSR_SISELECT         0x150
+#define CSR_SIREG2           0x152
+#define CSR_SIREG5           0x156
+#define CSR_MENVCFGH         0x31a
+#define CSR_MHPMEVENT3H      0x723
+
+/* Low-half selector writes preserve OF; high-half writes can clear it. */
+
+	.option	norvc
+	.option	norelax
+
+	.macro	check_selector_write low_csr, high_csr, first_failure
+	li	t4, \first_failure
+	li	t0, 8			/* mcountinhibit.HPM3 */
+	csrs	mcountinhibit, t0
+	li	t1, 1 << 29		/* SINH: still count in M-mode. */
+	csrw	CSR_MHPMEVENT3H, t1		/* mhpmevent3h: OF is clear. */
+	li	t1, 2			/* HW_INSTRUCTIONS */
+	csrw	mhpmevent3, t1
+	/* Set the counter to UINT64_MAX while inhibited. */
+	li	t2, -1
+	csrw	mhpmcounter3, t2
+	csrw	mhpmcounter3h, t2
+	li	t3, 1 << 13		/* mip.LCOFIP */
+	csrc	mip, t3
+
+	/*
+	 * With icount, this write accounts for the first increment after
+	 * enabling HPM3. The counter wraps, setting OF. Updating bits 31:0
+	 * must preserve both that OF and the existing SINH in bits 63:32.
+	 */
+	csrc	mcountinhibit, t0
+	csrw	\low_csr, t1
+	csrr	t2, CSR_MHPMEVENT3H
+	li	t1, 0xa0000000		/* OF | SINH */
+	bne	t1, t2, exit
+	csrs	mcountinhibit, t0
+
+	li	t4, \first_failure + 1
+	csrr	t2, mip
+	and	t2, t2, t3
+	beqz	t2, exit
+
+	/* Explicitly writing the high half must still be able to clear OF. */
+	li	t4, \first_failure + 2
+	li	t1, 1 << 29		/* Keep SINH, clear OF. */
+	csrw	\high_csr, t1
+	csrr	t2, CSR_MHPMEVENT3H
+	bne	t1, t2, exit
+
+	/* The high-half write must not change the selected event. */
+	li	t4, \first_failure + 3
+	csrr	t2, mhpmevent3
+	li	t1, 2
+	bne	t1, t2, exit
+	.endm
+
+	.text
+	.global _start
+_start:
+	/* Unexpected exceptions report the check in progress. */
+	li	t4, 9
+	lla	t0, exit
+	csrw	mtvec, t0
+	li	t0, 1 << 28		/* menvcfgh.CDE */
+	csrw	CSR_MENVCFGH, t0
+	li	t0, 8
+	csrw	mcounteren, t0		/* Delegate counter 3. */
+	li	t0, 0x43
+	csrw	CSR_SISELECT, t0		/* siselect: counter 3 */
+
+	/* Checks 1-4 use machine CSRs; checks 5-8 use delegated aliases. */
+	/* mhpmevent3, mhpmevent3h */
+	check_selector_write mhpmevent3, CSR_MHPMEVENT3H, 1
+	check_selector_write CSR_SIREG2, CSR_SIREG5, 5	/* sireg2, sireg5 */
+	li	t4, 0
+
+	.balign	4
+exit:
+	lla	a1, semiargs
+	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
+	sw	t0, 0(a1)
+	sw	t4, 4(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	8
diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
index 800c754093e275cd25be7878a96a8c551f73dbce..37cabafcc2b71a50a2f2a35731456fe7eecf01ff 100644
--- a/tests/tcg/riscv32/system/meson.build
+++ b/tests/tcg/riscv32/system/meson.build
@@ -22,6 +22,13 @@ tests += {
   },
 }
 
+tests += {
+  'pmu-fixed-rv32.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', '-icount', 'shift=0', qemu_args],
+  },
+}
+
 tests += {
   'smcdeleg-minh-rv32.S': {
     'cflags': cflags,
@@ -43,6 +50,13 @@ tests += {
   },
 }
 
+tests += {
+  'sscofpmf-event-rv32.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', '-icount', 'shift=0', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv32' in emulators
   tcg_tests += {
     'riscv32-softmmu': {
diff --git a/tests/tcg/riscv64/pmu-cycle-controls.S b/tests/tcg/riscv64/pmu-cycle-controls.S
new file mode 100644
index 0000000000000000000000000000000000000000..fdd14755f4d196dc2a3ec6c8b8540adc40b3a253
--- /dev/null
+++ b/tests/tcg/riscv64/pmu-cycle-controls.S
@@ -0,0 +1,80 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR number for older assemblers. */
+#define CSR_MCYCLECFG        0x321
+
+/* Check cycle-counter behavior across filter and inhibit control writes. */
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global _start
+_start:
+	/*
+	 * Failure bits:
+	 * 0: enabling MINH discards the previously accumulated value
+	 * 1: mcycle changes while M-mode is filtered
+	 * 2: disabling MINH adds the filtered interval
+	 * 3: mcycle does not resume after disabling MINH
+	 */
+	li	t4, 0
+	csrw	mcountinhibit, zero
+	csrw	CSR_MCYCLECFG, zero
+
+	/* Filtering M-mode must not discard the value accumulated so far. */
+	csrr	s0, mcycle
+	.rept	64
+	nop
+	.endr
+	li	t0, 1
+	slli	t0, t0, 62		/* MINH */
+	csrw	CSR_MCYCLECFG, t0
+	csrr	s1, mcycle
+	sltu	t1, s0, s1
+	xori	t1, t1, 1
+	or	t4, t4, t1
+	.rept	128
+	nop
+	.endr
+	csrr	s2, mcycle
+	xor	t1, s1, s2
+	sltu	t1, zero, t1
+	slli	t1, t1, 1
+	or	t4, t4, t1
+
+	/* Removing the filter must not add the inhibited interval. */
+	csrw	CSR_MCYCLECFG, zero
+	csrr	s3, mcycle
+	sub	t1, s3, s2
+	li	t2, 64
+	sltu	t1, t1, t2
+	xori	t1, t1, 1
+	slli	t1, t1, 2
+	or	t4, t4, t1
+	.rept	128
+	nop
+	.endr
+	csrr	t1, mcycle
+	sltu	t1, s3, t1
+	xori	t1, t1, 1
+	slli	t1, t1, 3
+	or	t4, t4, t1
+
+	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 cfb868c45704e210e8fe951be0cbb752b589d51d..1dd0402631036d0d377bb90d7cb1da2bae34ead4 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -113,6 +113,13 @@ tests += {
   },
 }
 
+tests += {
+  'pmu-cycle-controls.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



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 08/14] target/riscv: Require Sscofpmf for non-fixed event overflow
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (6 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 07/14] target/riscv: Preserve fixed counters across PMU state changes TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 09/14] target/riscv: Rebuild fixed-event PMU overflow deadlines TANG Tiancheng
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

riscv_pmu_incr_ctr() sets OF and LCOFIP on wrap even when Sscofpmf is
disabled. Use the shared overflow helper to require Sscofpmf and suppress
notifications while OF is set without stopping the counter.

Test DTLB overflow with Sscofpmf enabled and disabled, including
notification suppression and continued counting.

Fixes: 14664483457b ("target/riscv: Add sscofpmf extension support")
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/tcg/pmu.c                      |   6 +-
 tests/tcg/riscv64/sscofpmf-event-overflow.S | 103 ++++++++++++++++++++++++++++
 tests/tcg/riscv64/system/meson.build        |  15 ++++
 3 files changed, 119 insertions(+), 5 deletions(-)

diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 16942e53489cd5a2d2dd055fdffef07d04d59465..2f600c5a0fc2d7ba380ef34f89af6366e3e27884 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -448,11 +448,7 @@ int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
         counter = &env->pmu_ctrs[ctr_idx];
         if (counter->mhpmcounter_val == max_val) {
             counter->mhpmcounter_val = 0;
-            /* Generate interrupt only if OF bit is clear */
-            if (!(env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF)) {
-                env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
-                riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
-            }
+            riscv_pmu_set_overflow(env, ctr_idx);
         } else {
             counter->mhpmcounter_val++;
         }
diff --git a/tests/tcg/riscv64/sscofpmf-event-overflow.S b/tests/tcg/riscv64/sscofpmf-event-overflow.S
new file mode 100644
index 0000000000000000000000000000000000000000..cce06da10f9c7c20c18c7863b58b6dd724c45182
--- /dev/null
+++ b/tests/tcg/riscv64/sscofpmf-event-overflow.S
@@ -0,0 +1,103 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Non-fixed event overflow with and without Sscofpmf. */
+
+#ifndef EXPECT_SSCOFPMF
+#define EXPECT_SSCOFPMF 1
+#endif
+
+	.option	norvc
+	.option	norelax
+
+	.text
+	.global _start
+_start:
+	/* Unexpected exceptions report the current check number. */
+	li	t4, 1
+	lla	t0, exit
+	csrw	mtvec, t0
+	csrw	mie, zero
+	csrw	mip, zero
+	csrw	mcountinhibit, zero
+	csrw	mhpmevent3, zero
+	li	t0, -1
+	csrw	mhpmcounter3, t0
+	li	t0, 0x10019		/* DTLB read miss */
+	csrw	mhpmevent3, t0
+
+	/* One load after flushing the TLB must wrap the counter to zero. */
+	sfence.vma
+	lla	t0, first_page
+	lw	t1, 0(t0)
+	csrr	t0, mhpmcounter3
+	bnez	t0, exit
+
+	/* Only Sscofpmf turns that wrap into OF and LCOFIP. */
+	li	t4, 2
+	csrr	t0, mhpmevent3
+	srli	t0, t0, 63
+	li	t1, EXPECT_SSCOFPMF
+	bne	t0, t1, exit
+	li	t4, 3
+	csrr	t0, mip
+	srli	t0, t0, 13
+	andi	t0, t0, 1
+	bne	t0, t1, exit
+
+	/* Clearing LCOFIP alone must not re-enable overflow notification. */
+	li	t0, 1 << 13
+	csrc	mip, t0
+	li	t0, -1
+	csrw	mhpmcounter3, t0
+	sfence.vma
+	lla	t0, second_page
+	lw	t1, 0(t0)
+	li	t4, 4
+	csrr	t0, mhpmcounter3
+	bnez	t0, exit
+	li	t4, 5
+	csrr	t0, mhpmevent3
+	srli	t0, t0, 63
+	li	t1, EXPECT_SSCOFPMF
+	bne	t0, t1, exit
+	li	t4, 6
+	csrr	t0, mip
+	li	t1, 1 << 13
+	and	t0, t0, t1
+	bnez	t0, exit
+
+	/* Overflow notification must not stop event counting. */
+	sfence.vma
+	lla	t0, third_page
+	lw	t1, 0(t0)
+	li	t4, 7
+	csrr	t0, mhpmcounter3
+	li	t1, 1
+	bne	t0, t1, exit
+	li	t4, 0
+
+exit:
+	lla	a1, semiargs
+	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
+	sd	t0, 0(a1)
+	sd	t4, 8(a1)
+	li	a0, 0x20		/* TARGET_SYS_EXIT_EXTENDED */
+	.balign	16
+	slli	zero, zero, 0x1f
+	ebreak
+	srai	zero, zero, 0x7
+	j	.
+
+	.data
+	.balign	4096
+first_page:
+	.word	0
+	.balign	4096
+second_page:
+	.word	0
+	.balign	4096
+third_page:
+	.word	0
+	.balign	16
+semiargs:
+	.space	16
diff --git a/tests/tcg/riscv64/system/meson.build b/tests/tcg/riscv64/system/meson.build
index 1dd0402631036d0d377bb90d7cb1da2bae34ead4..13ee7954ef4f09559a02a3730fb017ccf2cfd882 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -99,6 +99,21 @@ tests += {
   },
 }
 
+tests += {
+  'sscofpmf-event-overflow.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max', qemu_args],
+  },
+}
+
+tests += {
+  'sscofpmf-event-overflow.S': {
+    'exe_name': 'sscofpmf-event-overflow-off',
+    'cflags': cflags + ['-DEXPECT_SSCOFPMF=0'],
+    'qemu_args': ['-cpu', 'max,sscofpmf=false', qemu_args],
+  },
+}
+
 tests += {
   'sscofpmf-cycle-overflow.S': {
     'cflags': cflags,

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 09/14] target/riscv: Rebuild fixed-event PMU overflow deadlines
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (7 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 08/14] target/riscv: Require Sscofpmf for non-fixed event overflow TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 10/14] target/riscv: Apply minstret exception accounting to HPM counters TANG Tiancheng
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

timer_mod_anticipate_ns() cannot postpone the shared overflow timer when
a counter is stopped or reconfigured. Recompute the earliest deadline
from all eligible counters after PMU changes and timer expiry. Check for
an actual counter wrap before setting OF or LCOFIP.

Keep counter arithmetic in source units. For timer scheduling, convert
only raw icount instruction counts to nanoseconds. Check how many fit
within INT64_MAX - now nanoseconds before conversion, using INT64_MAX as
the deadline if the count is larger. Recompute the remaining count at
expiry instead of keeping irq_overflow_left.

Icount time warp can expire the timer without executing instructions.
If the instruction source has not advanced, defer its next deadline
until execution resumes to avoid an endless warp/rearm loop.

Extend the cycle-control test to cover selector writes while CY is set
and resuming mcycle after CY is cleared.

Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/cpu.h                     |   4 +-
 target/riscv/tcg/cpu_helper.c          |   2 +
 target/riscv/tcg/pmu.c                 | 239 +++++++++++++--------------------
 target/riscv/tcg/pmu.h                 |   3 +-
 target/riscv/tcg/tcg-cpu.c             |  10 ++
 tests/tcg/riscv64/pmu-cycle-controls.S |  27 ++++
 6 files changed, 139 insertions(+), 146 deletions(-)

diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index f7b1bfc9cf5069125bc22dc2674d8e67431c5970..17b9929785f668487d2ec851b736d588e025fd91 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -236,8 +236,6 @@ typedef struct PMUCTRState {
     uint64_t mhpmcounter_val;
     /* Snapshot value of a counter */
     uint64_t mhpmcounter_prev;
-    /* Value beyond INT64_MAX before overflow interrupt trigger */
-    uint64_t irq_overflow_left;
 } PMUCTRState;
 
 typedef enum {
@@ -583,6 +581,8 @@ struct ArchCPU {
     RISCVSATPModes satp_modes;
 
     QEMUTimer *pmu_timer;
+    uint64_t pmu_timer_instret_snapshot;
+    bool pmu_timer_stalled;
     /* A bitmask of Available programmable counters */
     uint32_t pmu_avail_ctrs;
     /* Mapping of events to counters */
diff --git a/target/riscv/tcg/cpu_helper.c b/target/riscv/tcg/cpu_helper.c
index 07d92226527d85da93b8810e526d044e64fa4e3d..89751cdbf29f5bbd46d0d6b8c9d23eac5e3accac 100644
--- a/target/riscv/tcg/cpu_helper.c
+++ b/target/riscv/tcg/cpu_helper.c
@@ -889,6 +889,8 @@ void riscv_cpu_set_mode(CPURISCVState *env, privilege_mode_t newpriv,
             riscv_cpu_update_mip(env, 0, 0);
         }
     }
+
+    riscv_pmu_rebuild_timer(env);
 }
 
 /*
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 2f600c5a0fc2d7ba380ef34f89af6366e3e27884..f88f6ae671d877ed874d22c9d4b840edb6f433a5 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -26,13 +26,6 @@
 #include "system/device_tree.h"
 #include "system/cpu-timers.h"
 
-/*
- * 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)
 {
     if (ctr_idx < 3 || ctr_idx >= RV_MAX_MHPMCOUNTERS ||
@@ -324,7 +317,6 @@ void riscv_pmu_write_event(CPURISCVState *env, uint32_t ctr_idx,
                            uint64_t value, uint64_t wr_mask)
 {
     RISCVPMUFixedSnapshot snapshot;
-    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
 
     riscv_pmu_take_fixed_snapshot(env, &snapshot);
     if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
@@ -337,10 +329,7 @@ void riscv_pmu_write_event(CPURISCVState *env, uint32_t ctr_idx,
     if (riscv_pmu_fixed_ctr_enabled(env, ctr_idx)) {
         riscv_pmu_set_fixed_baseline(env, ctr_idx, &snapshot);
     }
-
-    if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
-        riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
-    }
+    riscv_pmu_rebuild_timer(env);
 }
 
 void riscv_pmu_write_counter(CPURISCVState *env, uint32_t ctr_idx,
@@ -349,23 +338,19 @@ void riscv_pmu_write_counter(CPURISCVState *env, uint32_t ctr_idx,
     RISCVPMUFixedSnapshot snapshot;
     PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
     bool rv32 = xl == MXL_RV32;
-    bool running;
     int start = upper_half ? 32 : 0;
     int length = rv32 ? 32 : 64;
 
     g_assert(rv32 || !upper_half);
 
     riscv_pmu_take_fixed_snapshot(env, &snapshot);
-    running = riscv_pmu_fixed_ctr_running(env, ctr_idx);
-    if (running) {
+    if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
         riscv_pmu_accumulate_fixed_delta(env, ctr_idx, &snapshot);
     }
     counter->mhpmcounter_val = deposit64(counter->mhpmcounter_val,
                                          start, length, value);
     /* mhpmcounter_prev tracks the source, not the written counter value. */
-    if (running && ctr_idx > 2) {
-        riscv_pmu_setup_timer(env, counter->mhpmcounter_val, ctr_idx);
-    }
+    riscv_pmu_rebuild_timer(env);
 }
 
 void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t value)
@@ -396,11 +381,8 @@ void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t value)
         if (riscv_pmu_fixed_ctr_enabled(env, ctr_idx)) {
             riscv_pmu_set_fixed_baseline(env, ctr_idx, &snapshot);
         }
-        if (ctr_idx > 2 && riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
-            riscv_pmu_setup_timer(env, env->pmu_ctrs[ctr_idx].mhpmcounter_val,
-                                  ctr_idx);
-        }
     }
+    riscv_pmu_rebuild_timer(env);
 }
 
 void riscv_pmu_decr_instret(CPURISCVState *env)
@@ -512,17 +494,6 @@ static bool riscv_pmu_event_supported(uint32_t event_idx)
     }
 }
 
-static int64_t pmu_ticks_to_ns(CPURISCVState *env, uint32_t ctr_idx,
-                               int64_t value)
-{
-    if (icount_enabled() &&
-        riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
-        return icount_to_ns(value);
-    }
-
-    return (NANOSECONDS_PER_SECOND / RISCV_PMU_HOST_TICK_HZ_ASSUMED) * value;
-}
-
 void riscv_pmu_rebuild_event_map(CPURISCVState *env)
 {
     uint32_t ctr_idx, ctr_mask, event_idx;
@@ -551,148 +522,132 @@ void riscv_pmu_rebuild_event_map(CPURISCVState *env)
     }
 }
 
-static bool pmu_hpmevent_set_of_if_clear(CPURISCVState *env, uint32_t ctr_idx)
-{
-    if (!get_field(env->mhpmevent_val[ctr_idx], MHPMEVENT_BIT_OF)) {
-        env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
-        return true;
-    } else {
-        return false;
-    }
-}
-
-static void pmu_timer_trigger_irq_counter(RISCVCPU *cpu, uint32_t ctr_idx)
+static int64_t riscv_pmu_overflow_delay_ns(CPURISCVState *env,
+                                           uint32_t ctr_idx,
+                                           uint64_t value, int64_t now)
 {
-    CPURISCVState *env = &cpu->env;
-    PMUCTRState *counter;
-    int64_t irq_trigger_at;
-    uint64_t curr_ctr_val, curr_ctrh_val;
-    uint64_t ctr_val;
+    uint64_t remaining;
+    uint64_t max_delay = INT64_MAX - now;
 
-    if (!riscv_pmu_counter_enabled(cpu, ctr_idx)) {
-        return;
+    if (!value) {
+        /* A complete 64-bit wrap is beyond the signed timer horizon. */
+        return max_delay;
     }
+    remaining = -value;
 
-    /* Generate interrupt only if OF bit is clear */
-    if (get_field(env->mhpmevent_val[ctr_idx], MHPMEVENT_BIT_OF)) {
-        return;
-    }
-
-    counter = &env->pmu_ctrs[ctr_idx];
-    if (counter->irq_overflow_left > 0) {
-        irq_trigger_at = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
-                        counter->irq_overflow_left;
-        timer_mod_anticipate_ns(cpu->pmu_timer, irq_trigger_at);
-        counter->irq_overflow_left = 0;
-        return;
-    }
+    if (icount_enabled() &&
+        riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
+        /* Use one adaptive-shift sample for both bounds and conversion. */
+        uint64_t ns_per_tick = icount_to_ns(1);
+        uint64_t max_ticks = max_delay / ns_per_tick;
 
-    riscv_pmu_read_ctr(env, (target_ulong *)&curr_ctr_val, false, ctr_idx,
-                        riscv_cpu_mxl(env));
-    ctr_val = counter->mhpmcounter_val;
-    if (riscv_cpu_mxl(env) == MXL_RV32) {
-        riscv_pmu_read_ctr(env, (target_ulong *)&curr_ctrh_val, true, ctr_idx,
-                            riscv_cpu_mxl(env));
-        curr_ctr_val = curr_ctr_val | (curr_ctrh_val << 32);
+        if (remaining > max_ticks) {
+            return max_delay;
+        }
+        return remaining * ns_per_tick;
     }
 
     /*
-     * We can not accommodate for inhibited modes when setting up timer. Check
-     * if the counter has actually overflowed or not by comparing current
-     * counter value (accommodated for inhibited modes) with software written
-     * counter value.
+     * Cycle under icount is already virtual ns.  Non-icount fixed events
+     * retain QEMU's existing one-host-tick-per-ns deadline approximation.
      */
-    if (curr_ctr_val >= ctr_val) {
-        riscv_pmu_setup_timer(env, curr_ctr_val, ctr_idx);
-        return;
-    }
-
-    if (cpu->pmu_avail_ctrs & BIT(ctr_idx)) {
-        if (pmu_hpmevent_set_of_if_clear(env, ctr_idx)) {
-            riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
-        }
-    }
+    return MIN(remaining, max_delay);
 }
 
-static void pmu_timer_trigger_irq(RISCVCPU *cpu,
-                                  enum riscv_pmu_event_idx evt_idx)
+static void riscv_pmu_rebuild_timer_internal(CPURISCVState *env,
+                                             bool timer_expired)
 {
+    RISCVCPU *cpu = env_archcpu(env);
+    RISCVPMUFixedSnapshot snapshot;
     uint32_t ctr_idx;
     uint32_t ctr_mask;
+    int64_t deadline = INT64_MAX;
+    int64_t now;
+    bool have_deadline = false;
+    bool timer_horizon_exhausted;
+    bool stalled = false;
 
-    if (evt_idx != RISCV_PMU_EVENT_HW_CPU_CYCLES &&
-        evt_idx != RISCV_PMU_EVENT_HW_INSTRUCTIONS) {
+    if (!cpu->pmu_timer) {
         return;
     }
 
-    ctr_mask = riscv_pmu_event_counter_mask(cpu, evt_idx);
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    /* No future absolute timer deadline is representable at this point. */
+    timer_horizon_exhausted = now == INT64_MAX;
+
+    ctr_mask = riscv_pmu_event_counter_mask(
+        cpu, RISCV_PMU_EVENT_HW_CPU_CYCLES);
+    ctr_mask |= riscv_pmu_event_counter_mask(
+        cpu, RISCV_PMU_EVENT_HW_INSTRUCTIONS);
 
     while (ctr_mask) {
+        PMUCTRState *counter;
+        int64_t candidate;
+
         ctr_idx = ctz32(ctr_mask);
         ctr_mask &= ~BIT(ctr_idx);
-        pmu_timer_trigger_irq_counter(cpu, ctr_idx);
-    }
-}
+        counter = &env->pmu_ctrs[ctr_idx];
 
-/* Timer callback for instret and cycle counter overflow */
-void riscv_pmu_timer_cb(void *priv)
-{
-    RISCVCPU *cpu = priv;
+        if (!riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+            continue;
+        }
+        riscv_pmu_accumulate_fixed_delta(env, ctr_idx, &snapshot);
+        if ((env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF) ||
+            riscv_pmu_counter_filtered(env,
+                                       env->mhpmevent_val[ctr_idx])) {
+            continue;
+        }
 
-    /* Timer event was triggered only for these events */
-    pmu_timer_trigger_irq(cpu, RISCV_PMU_EVENT_HW_CPU_CYCLES);
-    pmu_timer_trigger_irq(cpu, RISCV_PMU_EVENT_HW_INSTRUCTIONS);
-}
+        /*
+         * Settle current deltas and overflows even when no future deadline is
+         * representable.
+         */
+        if (timer_horizon_exhausted) {
+            continue;
+        }
 
-int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value, uint32_t ctr_idx)
-{
-    uint64_t overflow_delta, overflow_at, curr_ns;
-    int64_t overflow_ns, overflow_left = 0;
-    RISCVCPU *cpu = env_archcpu(env);
-    PMUCTRState *counter = &env->pmu_ctrs[ctr_idx];
+        if (timer_expired && icount_enabled() &&
+            riscv_pmu_ctr_monitor_instructions(env, ctr_idx) &&
+            snapshot.instret == cpu->pmu_timer_instret_snapshot) {
+            /*
+             * Icount can warp QEMU_CLOCK_VIRTUAL to this deadline without
+             * executing an instruction. Re-arming the unchanged instruction
+             * distance would create a warp/rearm loop; defer it until this
+             * CPU enters execution again.
+             */
+            stalled = true;
+            continue;
+        }
 
-    /* No need to setup a timer if LCOFI is disabled when OF is set */
-    if (!riscv_pmu_counter_valid(cpu, ctr_idx) || !cpu->cfg.ext_sscofpmf ||
-        get_field(env->mhpmevent_val[ctr_idx], MHPMEVENT_BIT_OF)) {
-        return -1;
+        candidate = now + riscv_pmu_overflow_delay_ns(
+                              env, ctr_idx, counter->mhpmcounter_val, now);
+        if (!have_deadline || candidate < deadline) {
+            deadline = candidate;
+            have_deadline = true;
+        }
     }
 
-    if (value) {
-        overflow_delta = UINT64_MAX - value + 1;
+    cpu->pmu_timer_instret_snapshot = snapshot.instret;
+    cpu->pmu_timer_stalled = stalled;
+    if (have_deadline) {
+        timer_mod_ns(cpu->pmu_timer, deadline);
     } else {
-        overflow_delta = UINT64_MAX;
-    }
-
-    /*
-     * QEMU supports only int64_t timers while RISC-V counters are uint64_t.
-     * Compute the leftover and save it so that it can be reprogrammed again
-     * when timer expires.
-     */
-    if (overflow_delta > INT64_MAX) {
-        overflow_left = overflow_delta - INT64_MAX;
+        timer_del(cpu->pmu_timer);
     }
+}
 
-    if (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
-        riscv_pmu_ctr_monitor_instructions(env, ctr_idx)) {
-        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;
-    }
-    curr_ns = (uint64_t)qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
-    overflow_at =  curr_ns + overflow_ns;
-    if (overflow_at <= curr_ns)
-        overflow_at = UINT64_MAX;
+void riscv_pmu_rebuild_timer(CPURISCVState *env)
+{
+    riscv_pmu_rebuild_timer_internal(env, false);
+}
 
-    if (overflow_at > INT64_MAX) {
-        overflow_left += overflow_at - INT64_MAX;
-        counter->irq_overflow_left = overflow_left;
-        overflow_at = INT64_MAX;
-    }
-    timer_mod_anticipate_ns(cpu->pmu_timer, overflow_at);
+/* Timer callback for instret and cycle counter overflow */
+void riscv_pmu_timer_cb(void *priv)
+{
+    RISCVCPU *cpu = priv;
 
-    return 0;
+    riscv_pmu_rebuild_timer_internal(&cpu->env, true);
 }
 
 
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index 1494fbc21f53137a90c1338f6ca8e3c3e750276a..d9238ae680f5e67031511db4f9afc2884212c5c2 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -44,12 +44,11 @@ void riscv_pmu_write_counter(CPURISCVState *env, uint32_t ctr_idx,
                              target_ulong value, bool upper_half, RISCVMXL xl);
 void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t value);
 void riscv_pmu_timer_cb(void *priv);
+void riscv_pmu_rebuild_timer(CPURISCVState *env);
 void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
 void riscv_pmu_rebuild_event_map(CPURISCVState *env);
 int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx);
 void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name);
-int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value,
-                          uint32_t ctr_idx);
 void riscv_pmu_update_fixed_ctrs(CPURISCVState *env, privilege_mode_t newpriv,
                                  bool new_virt);
 void riscv_pmu_decr_instret(CPURISCVState *env);
diff --git a/target/riscv/tcg/tcg-cpu.c b/target/riscv/tcg/tcg-cpu.c
index b68160af8307c1e46d3f200c5313823d240eb7b3..cdcb94f3bcaf0526512f1994e9f7127209e1adcb 100644
--- a/target/riscv/tcg/tcg-cpu.c
+++ b/target/riscv/tcg/tcg-cpu.c
@@ -247,6 +247,15 @@ static void riscv_restore_state_to_opc(CPUState *cs,
 }
 
 #ifndef CONFIG_USER_ONLY
+static void riscv_cpu_exec_enter(CPUState *cs)
+{
+    RISCVCPU *cpu = RISCV_CPU(cs);
+
+    if (cpu->pmu_timer_stalled) {
+        riscv_pmu_rebuild_timer(&cpu->env);
+    }
+}
+
 static vaddr riscv_pointer_wrap(CPUState *cs, int mmu_idx,
                                 vaddr result, vaddr base)
 {
@@ -283,6 +292,7 @@ const TCGCPUOps riscv_tcg_ops = {
     .mmu_index = riscv_cpu_mmu_index,
 
 #ifndef CONFIG_USER_ONLY
+    .cpu_exec_enter = riscv_cpu_exec_enter,
     .tlb_fill = riscv_cpu_tlb_fill,
     .pointer_wrap = riscv_pointer_wrap,
     .cpu_exec_interrupt = riscv_cpu_exec_interrupt,
diff --git a/tests/tcg/riscv64/pmu-cycle-controls.S b/tests/tcg/riscv64/pmu-cycle-controls.S
index fdd14755f4d196dc2a3ec6c8b8540adc40b3a253..474d46d61baa2c3ec55a5a40d7bdb7ddd3dd7f2f 100644
--- a/tests/tcg/riscv64/pmu-cycle-controls.S
+++ b/tests/tcg/riscv64/pmu-cycle-controls.S
@@ -17,6 +17,8 @@ _start:
 	 * 1: mcycle changes while M-mode is filtered
 	 * 2: disabling MINH adds the filtered interval
 	 * 3: mcycle does not resume after disabling MINH
+	 * 4: writing mhpmevent3 advances mcycle while CY is set
+	 * 5: mcycle does not resume after clearing CY
 	 */
 	li	t4, 0
 	csrw	mcountinhibit, zero
@@ -61,6 +63,31 @@ _start:
 	slli	t1, t1, 3
 	or	t4, t4, t1
 
+	/* Changing HPM3's event must leave mcycle stopped while CY is set. */
+	li	t0, 1			/* mcountinhibit.CY */
+	csrw	mcountinhibit, t0
+	csrr	s4, mcycle
+	.rept	128
+	nop
+	.endr
+	li	t0, 1			/* HW_CPU_CYCLES */
+	csrw	mhpmevent3, t0		/* mhpmevent3; rebuilds PMU timer */
+	csrr	s5, mcycle
+	xor	t1, s4, s5
+	sltu	t1, zero, t1
+	slli	t1, t1, 4
+	or	t4, t4, t1
+	csrw	mcountinhibit, zero
+	csrr	s6, mcycle
+	.rept	128
+	nop
+	.endr
+	csrr	t1, mcycle
+	sltu	t1, s6, t1
+	xori	t1, t1, 1
+	slli	t1, t1, 5
+	or	t4, t4, t1
+	csrw	mhpmevent3, zero
 	lla	a1, semiargs
 	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
 	sd	t0, 0(a1)

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 10/14] target/riscv: Apply minstret exception accounting to HPM counters
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (8 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 09/14] target/riscv: Rebuild fixed-event PMU overflow deadlines TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 11/14] target/riscv: Process PMU timer expiry on the owner vCPU TANG Tiancheng
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

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 <lyndra@linux.alibaba.com>
---
 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



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 11/14] target/riscv: Process PMU timer expiry on the owner vCPU
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (9 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 10/14] target/riscv: Apply minstret exception accounting to HPM counters TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 12/14] target/riscv: Migrate fixed PMU counter state TANG Tiancheng
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

The PMU timer callback can race MTTCG execution and CSR writes while
reading the event map and updating counters, OF and MIP.

Queue counter checks on the owning vCPU. The callback atomically sets
pmu_timer_work_pending and queues work only if it was previously false.
The vCPU clears the flag before checking counters and rebuilding the
timer. Earlier expiries are covered by that check; the first later expiry
queues another check.

Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/cpu.h     |  1 +
 target/riscv/tcg/pmu.c | 18 ++++++++++++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index 17b9929785f668487d2ec851b736d588e025fd91..a4d33f55c4e5cb6052cea6bee4c0afa46372b5c5 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -583,6 +583,7 @@ struct ArchCPU {
     QEMUTimer *pmu_timer;
     uint64_t pmu_timer_instret_snapshot;
     bool pmu_timer_stalled;
+    bool pmu_timer_work_pending;
     /* A bitmask of Available programmable counters */
     uint32_t pmu_avail_ctrs;
     /* Mapping of events to counters */
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 08298010b6d06f5792fa14ff81fe2f7a28c6476f..6286552a4ebf614df0252f84ddfadbc25d8d2258 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -663,15 +663,29 @@ void riscv_pmu_rebuild_timer(CPURISCVState *env)
     riscv_pmu_rebuild_timer_internal(env, false);
 }
 
+static void riscv_pmu_timer_work(CPUState *cs, run_on_cpu_data data)
+{
+    RISCVCPU *cpu = RISCV_CPU(cs);
+
+    /*
+     * An expiry before this exchange is covered by the following counter
+     * check. The first expiry after it sets pmu_timer_work_pending and queues
+     * another check.
+     */
+    qatomic_xchg(&cpu->pmu_timer_work_pending, false);
+    riscv_pmu_rebuild_timer_internal(&cpu->env, true);
+}
+
 /* Timer callback for instret and cycle counter overflow */
 void riscv_pmu_timer_cb(void *priv)
 {
     RISCVCPU *cpu = priv;
 
-    riscv_pmu_rebuild_timer_internal(&cpu->env, true);
+    if (!qatomic_xchg(&cpu->pmu_timer_work_pending, true)) {
+        async_run_on_cpu(CPU(cpu), riscv_pmu_timer_work, RUN_ON_CPU_NULL);
+    }
 }
 
-
 void riscv_pmu_init(RISCVCPU *cpu, Error **errp)
 {
     if (cpu->cfg.pmu_mask & (COUNTEREN_CY | COUNTEREN_TM | COUNTEREN_IR)) {

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 12/14] target/riscv: Migrate fixed PMU counter state
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (10 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 11/14] target/riscv: Process PMU timer expiry on the owner vCPU TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 13/14] target/riscv: Clear virtualization mode on reset TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 14/14] target/riscv: Preserve fixed PMU state across reset TANG Tiancheng
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

Migration saves fixed-source baselines but not the per-mode totals they
refer to, nor mcyclecfg/minstretcfg. TCG can therefore subtract an
unrelated baseline or apply the wrong privilege filter after loading.

Before saving, add pending increments allowed by the current filters to
each enabled fixed-source counter, including HPM cycle/instruction
counters. The per-mode totals then need not migrate.

On load, clear those totals and establish destination-local baselines.
Inhibited counters get a new baseline when enabled; other event counters
get one when switched to a fixed source. This excludes migration downtime
and avoids using the source QEMU's saved baselines.

Rebuild the event map and overflow timer. Recompute interrupt requests
because pre-save can set LCOFIP after cpu_common saved CPU_INTERRUPT_HARD.

Keep the existing main-section fields and add cpu/pmu-fixed to carry
mcyclecfg/minstretcfg and identify values that include pending increments.
All TCG CPUs send and require it because mcycle/minstret exist even
without Zicntr, Zihpm or HPM counters. Reject older TCG streams, whose
counter values cannot be reconstructed reliably. KVM PMU migration is
unchanged.

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4422
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4425
Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/cpu.h     |  1 +
 target/riscv/machine.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++----
 target/riscv/tcg/pmu.c | 79 ++++++++++++++++++++++++++++++++++++++++++++
 target/riscv/tcg/pmu.h |  2 ++
 4 files changed, 163 insertions(+), 7 deletions(-)

diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h
index a4d33f55c4e5cb6052cea6bee4c0afa46372b5c5..788d5a3ced45b32be05e29eaeb9ea3af0c0ccd6d 100644
--- a/target/riscv/cpu.h
+++ b/target/riscv/cpu.h
@@ -584,6 +584,7 @@ struct ArchCPU {
     uint64_t pmu_timer_instret_snapshot;
     bool pmu_timer_stalled;
     bool pmu_timer_work_pending;
+    bool pmu_fixed_subsection_present;
     /* A bitmask of Available programmable counters */
     uint32_t pmu_avail_ctrs;
     /* Mapping of events to counters */
diff --git a/target/riscv/machine.c b/target/riscv/machine.c
index b0ff2fc7f2ac10fab1f2ff845a953649091e1f43..7aa38b739cfd4d9274fe249eb914411e8a65b91f 100644
--- a/target/riscv/machine.c
+++ b/target/riscv/machine.c
@@ -267,6 +267,30 @@ static const VMStateDescription vmstate_kvm_mp_state = {
 };
 #endif
 
+static int riscv_cpu_pre_load(void *opaque)
+{
+    RISCVCPU *cpu = opaque;
+
+    cpu->pmu_fixed_subsection_present = false;
+#ifdef CONFIG_KVM
+    return riscv_cpu_kvm_pre_load(opaque);
+#else
+    return 0;
+#endif
+}
+
+static int riscv_cpu_pre_save(void *opaque)
+{
+#ifdef CONFIG_TCG
+    RISCVCPU *cpu = opaque;
+
+    if (tcg_enabled()) {
+        riscv_pmu_prepare_save(&cpu->env);
+    }
+#endif
+    return 0;
+}
+
 static bool debug_needed(void *opaque)
 {
     RISCVCPU *cpu = opaque;
@@ -308,16 +332,25 @@ static const VMStateDescription vmstate_debug = {
     }
 };
 
-static int riscv_cpu_post_load(void *opaque, int version_id)
+static bool riscv_cpu_post_load(void *opaque, int version_id, Error **errp)
 {
     RISCVCPU *cpu = opaque;
     CPURISCVState *env = &cpu->env;
 
     env->xl = cpu_recompute_xl(env);
 #ifdef CONFIG_TCG
-    riscv_pmu_rebuild_event_map(env);
+    if (tcg_enabled()) {
+        if (!cpu->pmu_fixed_subsection_present) {
+            error_setg(errp,
+                       "missing RISC-V fixed-counter PMU migration state");
+            return false;
+        }
+        riscv_pmu_complete_load(env);
+        /* PMU pre-save can raise an interrupt after cpu_common was saved. */
+        riscv_cpu_interrupt(env);
+    }
 #endif
-    return 0;
+    return true;
 }
 
 static bool smstateen_needed(void *opaque)
@@ -404,6 +437,42 @@ static const VMStateDescription vmstate_pmu_ctr_state = {
     }
 };
 
+static int pmu_fixed_post_load(void *opaque, int version_id)
+{
+    RISCVCPU *cpu = opaque;
+
+    /* Let the outer post-load distinguish this format from a legacy stream. */
+    cpu->pmu_fixed_subsection_present = true;
+    return 0;
+}
+
+static bool pmu_fixed_needed(void *opaque)
+{
+    /*
+     * KVM keeps PMU state in the kernel, not in the TCG counter model.
+     * TCG implements mcycle/minstret even without Zicntr, Zihpm or
+     * programmable counters.
+     */
+    return tcg_enabled();
+}
+
+/*
+ * This subsection identifies TCG streams whose fixed-source counter values
+ * include pending deltas. It also carries mcyclecfg and minstretcfg.
+ */
+static const VMStateDescription vmstate_pmu_fixed = {
+    .name = "cpu/pmu-fixed",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = pmu_fixed_needed,
+    .post_load = pmu_fixed_post_load,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT64(env.mcyclecfg, RISCVCPU),
+        VMSTATE_UINT64(env.minstretcfg, RISCVCPU),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
 static bool jvt_needed(void *opaque)
 {
     RISCVCPU *cpu = opaque;
@@ -505,10 +574,9 @@ const VMStateDescription vmstate_riscv_cpu = {
     .name = "cpu",
     .version_id = 12,
     .minimum_version_id = 12,
-#ifdef CONFIG_KVM
-    .pre_load = riscv_cpu_kvm_pre_load,
-#endif
-    .post_load = riscv_cpu_post_load,
+    .pre_load = riscv_cpu_pre_load,
+    .pre_save = riscv_cpu_pre_save,
+    .post_load_errp = riscv_cpu_post_load,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT64_ARRAY(env.gpr, RISCVCPU, 32),
         VMSTATE_UINT64_ARRAY(env.fpr, RISCVCPU, 32),
@@ -554,6 +622,11 @@ const VMStateDescription vmstate_riscv_cpu = {
         VMSTATE_UINT32(env.mcounteren, RISCVCPU),
         VMSTATE_UINT32(env.scountinhibit, RISCVCPU),
         VMSTATE_UINT32(env.mcountinhibit, RISCVCPU),
+        /*
+         * TCG includes pending fixed-source deltas in mhpmcounter_val
+         * before saving. After loading, it ignores mhpmcounter_prev and
+         * rebuilds the baseline from the destination source.
+         */
         VMSTATE_STRUCT_ARRAY(env.pmu_ctrs, RISCVCPU, RV_MAX_MHPMCOUNTERS, 0,
                              vmstate_pmu_ctr_state, PMUCTRState),
         VMSTATE_UINT64_ARRAY(env.mhpmevent_val, RISCVCPU, RV_MAX_MHPMEVENTS),
@@ -582,6 +655,7 @@ const VMStateDescription vmstate_riscv_cpu = {
         &vmstate_ctr,
         &vmstate_sstc,
         &vmstate_mseccfg,
+        &vmstate_pmu_fixed,
         NULL
     }
 };
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index 6286552a4ebf614df0252f84ddfadbc25d8d2258..df99b572a4c16cb1ac65c2f7cde35c6f8349e681 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -663,6 +663,85 @@ void riscv_pmu_rebuild_timer(CPURISCVState *env)
     riscv_pmu_rebuild_timer_internal(env, false);
 }
 
+static uint32_t riscv_pmu_fixed_source_counter_mask(CPURISCVState *env)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    uint32_t mask = COUNTEREN_CY | COUNTEREN_IR;
+
+    mask |= riscv_pmu_event_counter_mask(
+        cpu, RISCV_PMU_EVENT_HW_CPU_CYCLES);
+    mask |= riscv_pmu_event_counter_mask(
+        cpu, RISCV_PMU_EVENT_HW_INSTRUCTIONS);
+    return mask;
+}
+
+static void riscv_pmu_accumulate_fixed_source_counters(
+    CPURISCVState *env, const RISCVPMUFixedSnapshot *snapshot)
+{
+    uint32_t mask = riscv_pmu_fixed_source_counter_mask(env);
+
+    while (mask) {
+        uint32_t ctr_idx = ctz32(mask);
+
+        mask &= ~BIT(ctr_idx);
+        if (riscv_pmu_fixed_ctr_running(env, ctr_idx)) {
+            riscv_pmu_accumulate_fixed_delta(env, ctr_idx, snapshot);
+        }
+    }
+}
+
+void riscv_pmu_prepare_save(CPURISCVState *env)
+{
+    RISCVPMUFixedSnapshot snapshot;
+
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    riscv_pmu_accumulate_fixed_source_counters(env, &snapshot);
+}
+
+static void riscv_pmu_rebase_fixed_source_counters(
+    CPURISCVState *env, const RISCVPMUFixedSnapshot *snapshot)
+{
+    uint32_t mask;
+
+    memset(env->pmu_fixed_ctrs, 0, sizeof(env->pmu_fixed_ctrs));
+    if (env->virt_enabled) {
+        env->pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_CYCLE]
+            .counter_virt_prev[env->priv] = snapshot->cycle;
+        env->pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_INSTRET]
+            .counter_virt_prev[env->priv] = snapshot->instret;
+    } else {
+        env->pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_CYCLE]
+            .counter_prev[env->priv] = snapshot->cycle;
+        env->pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_INSTRET]
+            .counter_prev[env->priv] = snapshot->instret;
+    }
+
+    mask = riscv_pmu_fixed_source_counter_mask(env);
+    while (mask) {
+        uint32_t ctr_idx = ctz32(mask);
+
+        mask &= ~BIT(ctr_idx);
+        if (riscv_pmu_fixed_ctr_enabled(env, ctr_idx)) {
+            riscv_pmu_set_fixed_baseline(env, ctr_idx, snapshot);
+        }
+    }
+}
+
+void riscv_pmu_complete_load(CPURISCVState *env)
+{
+    RISCVCPU *cpu = env_archcpu(env);
+    RISCVPMUFixedSnapshot snapshot;
+
+    riscv_pmu_rebuild_event_map(env);
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    riscv_pmu_rebase_fixed_source_counters(env, &snapshot);
+
+    qatomic_set(&cpu->pmu_timer_work_pending, false);
+    cpu->pmu_timer_stalled = false;
+    cpu->pmu_timer_instret_snapshot = snapshot.instret;
+    riscv_pmu_rebuild_timer(env);
+}
+
 static void riscv_pmu_timer_work(CPUState *cs, run_on_cpu_data data)
 {
     RISCVCPU *cpu = RISCV_CPU(cs);
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index d9238ae680f5e67031511db4f9afc2884212c5c2..1cfe6acf55b5468f5c00c4a136ece88981384341 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -45,6 +45,8 @@ void riscv_pmu_write_counter(CPURISCVState *env, uint32_t ctr_idx,
 void riscv_pmu_write_inhibit(CPURISCVState *env, uint32_t value);
 void riscv_pmu_timer_cb(void *priv);
 void riscv_pmu_rebuild_timer(CPURISCVState *env);
+void riscv_pmu_prepare_save(CPURISCVState *env);
+void riscv_pmu_complete_load(CPURISCVState *env);
 void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
 void riscv_pmu_rebuild_event_map(CPURISCVState *env);
 int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx);

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 13/14] target/riscv: Clear virtualization mode on reset
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (11 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 12/14] target/riscv: Migrate fixed PMU counter state TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  2026-09-10 14:39 ` [PATCH v2 14/14] target/riscv: Preserve fixed PMU state across reset TANG Tiancheng
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

Reset enters M-mode without clearing virt_enabled, leaving an invalid
M-mode, V=1 state after a reset from virtual mode. Clear virt_enabled
alongside the privilege reset.

Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/cpu.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index 5fff9d745e9b36d9cbd30f166ec91b5165e5f497..f60d7cca1e2f0008b09b1055c5cdeed3ec4ad1d3 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -980,6 +980,7 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
 #ifndef CONFIG_USER_ONLY
     env->misa_mxl = mcc->def->misa_mxl_max;
     env->priv = PRV_M;
+    env->virt_enabled = false;
     env->mstatus &= ~(MSTATUS_MIE | MSTATUS_MPRV);
     if (env->misa_mxl > MXL_RV32) {
         /*

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v2 14/14] target/riscv: Preserve fixed PMU state across reset
  2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
                   ` (12 preceding siblings ...)
  2026-09-10 14:39 ` [PATCH v2 13/14] target/riscv: Clear virtualization mode on reset TANG Tiancheng
@ 2026-09-10 14:39 ` TANG Tiancheng
  13 siblings, 0 replies; 20+ messages in thread
From: TANG Tiancheng @ 2026-09-10 14:39 UTC (permalink / raw)
  To: qemu-devel
  Cc: Zephyr Li, Palmer Dabbelt, Alistair Francis, Weiwei Li,
	Daniel Henrique Barboza, Liu Zhiwei, Chao Liu, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé,
	TANG Tiancheng

Privilege filtering records cycle/instruction increments per mode. Reset
overwrites privilege/V without adding the final interval to the old
mode's total, so a counter filtering for that mode loses those counts.

Account for the old mode before entering M-mode with V=0 and rebuild the
overflow timer after reset. On initial reset, only initialize baselines:
no guest code has run, so pre-execution QEMU time must not count.
Preserve architectural counter and selector state, as before.

Test that a VS-only cycle counter retains its counts across reset.

Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
---
 target/riscv/cpu.c                   |  16 ++++++
 target/riscv/tcg/pmu.c               |  11 ++++
 target/riscv/tcg/pmu.h               |   1 +
 tests/tcg/riscv64/pmu-reset-vs.S     | 105 +++++++++++++++++++++++++++++++++++
 tests/tcg/riscv64/system/meson.build |   7 +++
 5 files changed, 140 insertions(+)

diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c
index f60d7cca1e2f0008b09b1055c5cdeed3ec4ad1d3..e27312ac505dbee072c5a3c52073b00d703fd81d 100644
--- a/target/riscv/cpu.c
+++ b/target/riscv/cpu.c
@@ -42,6 +42,9 @@
 #include "disas/capstone.h"
 #if !defined(CONFIG_USER_ONLY)
 #include "target/riscv/tcg/debug.h"
+#ifdef CONFIG_TCG
+#include "target/riscv/tcg/pmu.h"
+#endif
 #endif
 
 /* RISC-V CPU definitions */
@@ -979,6 +982,16 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
     }
 #ifndef CONFIG_USER_ONLY
     env->misa_mxl = mcc->def->misa_mxl_max;
+#ifdef CONFIG_TCG
+    /* The initial reset has no guest execution to count. */
+    if (tcg_enabled()) {
+        if (qdev_is_realized(DEVICE(cpu))) {
+            riscv_pmu_update_fixed_ctrs(env, PRV_M, false);
+        } else {
+            riscv_pmu_init_fixed_counter_baselines(env);
+        }
+    }
+#endif
     env->priv = PRV_M;
     env->virt_enabled = false;
     env->mstatus &= ~(MSTATUS_MIE | MSTATUS_MPRV);
@@ -1092,6 +1105,9 @@ static void riscv_cpu_reset_hold(Object *obj, ResetType type)
 
 #ifndef CONFIG_USER_ONLY
 #ifdef CONFIG_TCG
+    if (tcg_enabled()) {
+        riscv_pmu_rebuild_timer(env);
+    }
     if (cpu->cfg.debug || cpu->cfg.ext_sdtrig) {
         riscv_trigger_reset_hold(env);
     }
diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
index df99b572a4c16cb1ac65c2f7cde35c6f8349e681..1d692a0a8e3f7759e033214d579103627d070b5b 100644
--- a/target/riscv/tcg/pmu.c
+++ b/target/riscv/tcg/pmu.c
@@ -171,6 +171,17 @@ void riscv_pmu_update_fixed_ctrs(CPURISCVState *env,
     riscv_pmu_update_fixed_ctrs_snapshot(env, newpriv, new_virt, &snapshot);
 }
 
+void riscv_pmu_init_fixed_counter_baselines(CPURISCVState *env)
+{
+    RISCVPMUFixedSnapshot snapshot;
+
+    riscv_pmu_take_fixed_snapshot(env, &snapshot);
+    env->pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_CYCLE]
+        .counter_prev[PRV_M] = snapshot.cycle;
+    env->pmu_fixed_ctrs[RISCV_PMU_FIXED_DOMAIN_INSTRET]
+        .counter_prev[PRV_M] = snapshot.instret;
+}
+
 uint64_t
 riscv_pmu_ctr_get_fixed_value(CPURISCVState *env, uint32_t ctr_idx,
                               const RISCVPMUFixedSnapshot *snapshot)
diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
index 1cfe6acf55b5468f5c00c4a136ece88981384341..fac84dbb1a8a6c637f7241ca27910e5e212223e9 100644
--- a/target/riscv/tcg/pmu.h
+++ b/target/riscv/tcg/pmu.h
@@ -53,6 +53,7 @@ int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx);
 void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name);
 void riscv_pmu_update_fixed_ctrs(CPURISCVState *env, privilege_mode_t newpriv,
                                  bool new_virt);
+void riscv_pmu_init_fixed_counter_baselines(CPURISCVState *env);
 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/pmu-reset-vs.S b/tests/tcg/riscv64/pmu-reset-vs.S
new file mode 100644
index 0000000000000000000000000000000000000000..d216faef17a2a395120cb3a71166a821a43dee88
--- /dev/null
+++ b/tests/tcg/riscv64/pmu-reset-vs.S
@@ -0,0 +1,105 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* CSR number for older assemblers. */
+#define CSR_MCYCLECFG        0x321
+
+	.option	norvc
+	.option	norelax
+
+	.equ	MSTATUS_MPP_S,  (1 << 11)
+	.equ	MSTATUS_MPP,    (3 << 11)
+	.equ	MSTATUS_SPP,    (1 << 8)
+	.equ	HSTATUS_SPV,    (1 << 7)
+	.equ	SIFIVE_TEST,    0x100000
+	.equ	MTIMECMP,       0x2004000
+	.equ	FINISHER_RESET, 0x7777
+
+	.text
+	.global _start
+_start:
+	/* Preserve the reset marker and saved count in MTIMECMP. */
+	li	t0, MTIMECMP
+	ld	t1, 0(t0)
+	srli	t2, t1, 48
+	li	t3, 0xa5a5
+	beq	t2, t3, after_reset
+
+	/* Count VS only, then remember the value before entering VS-mode. */
+	li	t0, 0x1d		/* MINH | SINH | UINH | VUINH */
+	slli	t0, t0, 58
+	csrw	CSR_MCYCLECFG, t0
+	csrr	t1, mcycle
+	slli	t1, t1, 16
+	srli	t1, t1, 16
+	li	t2, 0xa5a5
+	slli	t2, t2, 48
+	or	t1, t1, t2
+	li	t0, MTIMECMP
+	sd	t1, 0(t0)
+	li	t0, -1
+	csrw	pmpaddr0, t0
+	li	t0, 0x1f		/* RWX, NAPOT */
+	csrw	pmpcfg0, t0
+
+	/* Enter HS-mode first. */
+	csrr	t0, mstatus
+	li	t1, MSTATUS_MPP
+	not	t1, t1
+	and	t0, t0, t1
+	li	t1, MSTATUS_MPP_S
+	or	t0, t0, t1
+	csrw	mstatus, t0
+	lla	t0, hs_enter
+	csrw	mepc, t0
+	mret
+
+hs_enter:
+	li	t0, HSTATUS_SPV
+	csrs	hstatus, t0
+	li	t0, MSTATUS_SPP
+	csrs	sstatus, t0
+	lla	t0, vs_reset
+	csrw	sepc, t0
+	sret
+
+vs_reset:
+	/* These VS-mode cycles must remain in mcycle after reset. */
+	li	t0, 128
+1:
+	addi	t0, t0, -1
+	bnez	t0, 1b
+
+	li	t0, SIFIVE_TEST
+	li	t1, FINISHER_RESET
+	sw	t1, 0(t0)
+	j	.
+
+after_reset:
+	csrr	t0, mcycle
+	slli	t1, t1, 16		/* Clear the MTIMECMP marker. */
+	srli	t1, t1, 16
+	addi	t1, t1, 128
+	bgeu	t1, t0, fail
+	li	t3, 0
+	j	exit
+
+fail:
+	li	t3, 1
+
+exit:
+	lla	a1, semiargs
+	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
+	sd	t0, 0(a1)
+	sd	t3, 8(a1)
+	li	a0, 0x20		/* TARGET_SYS_EXIT_EXTENDED */
+
+	.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 d2355090121ba7a7d0046c1fc1824d6a89c1267f..3f2043d100ff88cc39a21002a1e90ccab319001b 100644
--- a/tests/tcg/riscv64/system/meson.build
+++ b/tests/tcg/riscv64/system/meson.build
@@ -142,6 +142,13 @@ tests += {
   },
 }
 
+tests += {
+  'pmu-reset-vs.S': {
+    'cflags': cflags,
+    'qemu_args': ['-cpu', 'max,smcntrpmf=true', '-icount', 'shift=0', qemu_args],
+  },
+}
+
 if 'qemu-system-riscv64' in emulators
   tcg_tests += {
     'riscv64-softmmu': {

-- 
2.43.0



^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes
  2026-09-10 14:39 ` [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes TANG Tiancheng
@ 2026-09-11  3:47   ` Chao Liu
  0 siblings, 0 replies; 20+ messages in thread
From: Chao Liu @ 2026-09-11  3:47 UTC (permalink / raw)
  To: TANG Tiancheng
  Cc: qemu-devel, Zephyr Li, Palmer Dabbelt, Alistair Francis,
	Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé

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 <lyndra@linux.alibaba.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>

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
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 02/14] target/riscv: Support multiple counters per PMU event
  2026-09-10 14:39 ` [PATCH v2 02/14] target/riscv: Support multiple counters per PMU event TANG Tiancheng
@ 2026-09-11  3:54   ` Chao Liu
  0 siblings, 0 replies; 20+ messages in thread
From: Chao Liu @ 2026-09-11  3:54 UTC (permalink / raw)
  To: TANG Tiancheng
  Cc: qemu-devel, Zephyr Li, Palmer Dabbelt, Alistair Francis,
	Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé

On Thu, Sep 10, 2026 at 10:39:39PM +0800, TANG Tiancheng wrote:
> The PMU FDT lists multiple eligible counters for each event, but the
> event map stores only one counter per event. A second selector for the
> same event is accepted by the CSR but ignored by the map, so its counter
> does not count or overflow. Changing a selector between nonzero events
> also leaves the old mapping.
> 
> Store a counter mask per event and rebuild the map from mhpmevent CSRs
> after selector writes and migration. Update event delivery, fixed-source
> accounting and overflow handling to cover every mapped counter.
> 
> Test selector replacement and multiple counters selecting instructions
> or DTLB misses.
> 
> Fixes: 14664483457b ("target/riscv: Add sscofpmf extension support")
> Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>

Thanks,
Chao

> ---
>  target/riscv/machine.c                |   6 ++
>  target/riscv/tcg/csr.c                |   2 +-
>  target/riscv/tcg/pmu.c                | 182 +++++++++++++++++-----------------
>  target/riscv/tcg/pmu.h                |   3 +-
>  tests/tcg/riscv64/sscofpmf-overflow.S |  80 ++++++++++++++-
>  5 files changed, 176 insertions(+), 97 deletions(-)
> 
> diff --git a/target/riscv/machine.c b/target/riscv/machine.c
> index bf203bffcefb32710ed0f2af4d4f4595e122d1d9..b0ff2fc7f2ac10fab1f2ff845a953649091e1f43 100644
> --- a/target/riscv/machine.c
> +++ b/target/riscv/machine.c
> @@ -24,6 +24,9 @@
>  #include "migration/cpu.h"
>  #include "exec/icount.h"
>  #include "target/riscv/tcg/debug.h"
> +#ifdef CONFIG_TCG
> +#include "target/riscv/tcg/pmu.h"
> +#endif
>  #ifdef CONFIG_KVM
>  #include "kvm/kvm_riscv.h"
>  #endif
> @@ -311,6 +314,9 @@ static int riscv_cpu_post_load(void *opaque, int version_id)
>      CPURISCVState *env = &cpu->env;
>  
>      env->xl = cpu_recompute_xl(env);
> +#ifdef CONFIG_TCG
> +    riscv_pmu_rebuild_event_map(env);
> +#endif
>      return 0;
>  }
>  
> diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
> index 52664a26f5a97a5dc8ff37abf99b4d10927fb120..d15a2d096cb6e13cd123ff9ae82ee7c643c2a961 100644
> --- a/target/riscv/tcg/csr.c
> +++ b/target/riscv/tcg/csr.c
> @@ -1241,7 +1241,7 @@ static void riscv_pmu_write_mhpmevent(CPURISCVState *env,
>      }
>  
>      env->mhpmevent_val[ctr_idx] = value;
> -    riscv_pmu_update_event_map(env, value, ctr_idx);
> +    riscv_pmu_rebuild_event_map(env);
>  
>      if (enabled &&
>          (riscv_pmu_ctr_monitor_cycles(env, ctr_idx) ||
> diff --git a/target/riscv/tcg/pmu.c b/target/riscv/tcg/pmu.c
> index 1a4658319b11a9a8a0edef18fc8a5abd0027eb31..f19f417e90e33a94d00007ef132ef4e154175b19 100644
> --- a/target/riscv/tcg/pmu.c
> +++ b/target/riscv/tcg/pmu.c
> @@ -49,6 +49,17 @@ static bool riscv_pmu_counter_enabled(RISCVCPU *cpu, uint32_t ctr_idx)
>      }
>  }
>  
> +static uint32_t riscv_pmu_event_counter_mask(RISCVCPU *cpu,
> +                                             uint32_t event_idx)
> +{
> +    if (!cpu->pmu_event_ctr_map) {
> +        return 0;
> +    }
> +
> +    return GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
> +                                                GUINT_TO_POINTER(event_idx)));
> +}
> +
>  static bool riscv_pmu_counter_filtered(CPURISCVState *env, uint64_t cfg)
>  {
>      bool virt_on = env->virt_enabled;
> @@ -180,41 +191,41 @@ void riscv_pmu_decr_instret(CPURISCVState *env)
>  
>  int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx)
>  {
> -    uint32_t ctr_idx;
> +    uint32_t ctr_idx, ctr_mask;
>      CPURISCVState *env = &cpu->env;
>      uint64_t max_val = UINT64_MAX;
>      PMUCTRState *counter;
> -    gpointer value;
>  
>      if (!cpu->cfg.pmu_mask) {
>          return 0;
>      }
> -    value = g_hash_table_lookup(cpu->pmu_event_ctr_map,
> -                                GUINT_TO_POINTER(event_idx));
> -    if (!value) {
> -        return -1;
> -    }
>  
> -    ctr_idx = GPOINTER_TO_UINT(value);
> -    if (!riscv_pmu_counter_enabled(cpu, ctr_idx)) {
> +    ctr_mask = riscv_pmu_event_counter_mask(cpu, event_idx);
> +    if (!ctr_mask) {
>          return -1;
>      }
>  
> -    if (riscv_pmu_counter_filtered(env, env->mhpmevent_val[ctr_idx])) {
> -        return 0;
> -    }
> +    while (ctr_mask) {
> +        ctr_idx = ctz32(ctr_mask);
> +        ctr_mask &= ~BIT(ctr_idx);
>  
> -    /* Handle the overflow scenario */
> -    counter = &env->pmu_ctrs[ctr_idx];
> -    if (counter->mhpmcounter_val == max_val) {
> -        counter->mhpmcounter_val = 0;
> -        /* Generate interrupt only if OF bit is clear */
> -        if (!(env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF)) {
> -            env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
> -            riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
> +        if (!riscv_pmu_counter_enabled(cpu, ctr_idx) ||
> +            riscv_pmu_counter_filtered(env, env->mhpmevent_val[ctr_idx])) {
> +            continue;
> +        }
> +
> +        /* Handle the overflow scenario */
> +        counter = &env->pmu_ctrs[ctr_idx];
> +        if (counter->mhpmcounter_val == max_val) {
> +            counter->mhpmcounter_val = 0;
> +            /* Generate interrupt only if OF bit is clear */
> +            if (!(env->mhpmevent_val[ctr_idx] & MHPMEVENT_BIT_OF)) {
> +                env->mhpmevent_val[ctr_idx] |= MHPMEVENT_BIT_OF;
> +                riscv_cpu_update_mip(env, MIP_LCOFIP, BOOL_TO_MASK(1));
> +            }
> +        } else {
> +            counter->mhpmcounter_val++;
>          }
> -    } else {
> -        counter->mhpmcounter_val++;
>      }
>  
>      return 0;
> @@ -224,8 +235,7 @@ bool riscv_pmu_ctr_monitor_instructions(CPURISCVState *env,
>                                          uint32_t target_ctr)
>  {
>      RISCVCPU *cpu;
> -    uint32_t event_idx;
> -    uint32_t ctr_idx;
> +    uint32_t ctr_mask;
>  
>      /* Fixed instret counter */
>      if (target_ctr == 2) {
> @@ -237,21 +247,15 @@ bool riscv_pmu_ctr_monitor_instructions(CPURISCVState *env,
>          return false;
>      }
>  
> -    event_idx = RISCV_PMU_EVENT_HW_INSTRUCTIONS;
> -    ctr_idx = GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
> -                               GUINT_TO_POINTER(event_idx)));
> -    if (!ctr_idx) {
> -        return false;
> -    }
> -
> -    return target_ctr == ctr_idx ? true : false;
> +    ctr_mask = riscv_pmu_event_counter_mask(cpu,
> +                                            RISCV_PMU_EVENT_HW_INSTRUCTIONS);
> +    return (ctr_mask & BIT(target_ctr)) != 0;
>  }
>  
>  bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env, uint32_t target_ctr)
>  {
>      RISCVCPU *cpu;
> -    uint32_t event_idx;
> -    uint32_t ctr_idx;
> +    uint32_t ctr_mask;
>  
>      /* Fixed mcycle counter */
>      if (target_ctr == 0) {
> @@ -263,22 +267,23 @@ bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env, uint32_t target_ctr)
>          return false;
>      }
>  
> -    event_idx = RISCV_PMU_EVENT_HW_CPU_CYCLES;
> -    ctr_idx = GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
> -                               GUINT_TO_POINTER(event_idx)));
> -
> -    /* Counter zero is not used for event_ctr_map */
> -    if (!ctr_idx) {
> -        return false;
> -    }
> -
> -    return (target_ctr == ctr_idx) ? true : false;
> +    ctr_mask = riscv_pmu_event_counter_mask(cpu,
> +                                            RISCV_PMU_EVENT_HW_CPU_CYCLES);
> +    return (ctr_mask & BIT(target_ctr)) != 0;
>  }
>  
> -static gboolean pmu_remove_event_map(gpointer key, gpointer value,
> -                                     gpointer udata)
> +static bool riscv_pmu_event_supported(uint32_t event_idx)
>  {
> -    return (GPOINTER_TO_UINT(value) == GPOINTER_TO_UINT(udata)) ? true : false;
> +    switch (event_idx) {
> +    case RISCV_PMU_EVENT_HW_CPU_CYCLES:
> +    case RISCV_PMU_EVENT_HW_INSTRUCTIONS:
> +    case RISCV_PMU_EVENT_CACHE_DTLB_READ_MISS:
> +    case RISCV_PMU_EVENT_CACHE_DTLB_WRITE_MISS:
> +    case RISCV_PMU_EVENT_CACHE_ITLB_PREFETCH_MISS:
> +        return true;
> +    default:
> +        return false;
> +    }
>  }
>  
>  static int64_t pmu_icount_ticks_to_ns(int64_t value)
> @@ -294,48 +299,32 @@ static int64_t pmu_icount_ticks_to_ns(int64_t value)
>      return ret;
>  }
>  
> -int riscv_pmu_update_event_map(CPURISCVState *env, uint64_t value,
> -                               uint32_t ctr_idx)
> +void riscv_pmu_rebuild_event_map(CPURISCVState *env)
>  {
> -    uint32_t event_idx;
> +    uint32_t ctr_idx, ctr_mask, event_idx;
>      RISCVCPU *cpu = env_archcpu(env);
>  
> -    if (!riscv_pmu_counter_valid(cpu, ctr_idx) || !cpu->pmu_event_ctr_map) {
> -        return -1;
> +    if (!cpu->pmu_event_ctr_map) {
> +        return;
>      }
>  
> -    /*
> -     * Expected mhpmevent value is zero for reset case. Remove the current
> -     * mapping.
> -     */
> -    if (!(value & MHPMEVENT_IDX_MASK)) {
> -        g_hash_table_foreach_remove(cpu->pmu_event_ctr_map,
> -                                    pmu_remove_event_map,
> -                                    GUINT_TO_POINTER(ctr_idx));
> -        return 0;
> -    }
> +    g_hash_table_remove_all(cpu->pmu_event_ctr_map);
> +    for (ctr_idx = 3; ctr_idx < RV_MAX_MHPMCOUNTERS; ctr_idx++) {
> +        if (!riscv_pmu_counter_valid(cpu, ctr_idx)) {
> +            continue;
> +        }
>  
> -    event_idx = value & MHPMEVENT_IDX_MASK;
> -    if (g_hash_table_lookup(cpu->pmu_event_ctr_map,
> -                            GUINT_TO_POINTER(event_idx))) {
> -        return 0;
> -    }
> +        event_idx = env->mhpmevent_val[ctr_idx] & MHPMEVENT_IDX_MASK;
> +        if (!event_idx || !riscv_pmu_event_supported(event_idx)) {
> +            continue;
> +        }
>  
> -    switch (event_idx) {
> -    case RISCV_PMU_EVENT_HW_CPU_CYCLES:
> -    case RISCV_PMU_EVENT_HW_INSTRUCTIONS:
> -    case RISCV_PMU_EVENT_CACHE_DTLB_READ_MISS:
> -    case RISCV_PMU_EVENT_CACHE_DTLB_WRITE_MISS:
> -    case RISCV_PMU_EVENT_CACHE_ITLB_PREFETCH_MISS:
> -        break;
> -    default:
> -        /* We don't support any raw events right now */
> -        return -1;
> +        ctr_mask = riscv_pmu_event_counter_mask(cpu, event_idx);
> +        ctr_mask |= BIT(ctr_idx);
> +        g_hash_table_insert(cpu->pmu_event_ctr_map,
> +                            GUINT_TO_POINTER(event_idx),
> +                            GUINT_TO_POINTER(ctr_mask));
>      }
> -    g_hash_table_insert(cpu->pmu_event_ctr_map, GUINT_TO_POINTER(event_idx),
> -                        GUINT_TO_POINTER(ctr_idx));
> -
> -    return 0;
>  }
>  
>  static bool pmu_hpmevent_set_of_if_clear(CPURISCVState *env, uint32_t ctr_idx)
> @@ -348,23 +337,14 @@ static bool pmu_hpmevent_set_of_if_clear(CPURISCVState *env, uint32_t ctr_idx)
>      }
>  }
>  
> -static void pmu_timer_trigger_irq(RISCVCPU *cpu,
> -                                  enum riscv_pmu_event_idx evt_idx)
> +static void pmu_timer_trigger_irq_counter(RISCVCPU *cpu, uint32_t ctr_idx)
>  {
> -    uint32_t ctr_idx;
>      CPURISCVState *env = &cpu->env;
>      PMUCTRState *counter;
>      int64_t irq_trigger_at;
>      uint64_t curr_ctr_val, curr_ctrh_val;
>      uint64_t ctr_val;
>  
> -    if (evt_idx != RISCV_PMU_EVENT_HW_CPU_CYCLES &&
> -        evt_idx != RISCV_PMU_EVENT_HW_INSTRUCTIONS) {
> -        return;
> -    }
> -
> -    ctr_idx = GPOINTER_TO_UINT(g_hash_table_lookup(cpu->pmu_event_ctr_map,
> -                               GUINT_TO_POINTER(evt_idx)));
>      if (!riscv_pmu_counter_enabled(cpu, ctr_idx)) {
>          return;
>      }
> @@ -408,6 +388,26 @@ static void pmu_timer_trigger_irq(RISCVCPU *cpu,
>      }
>  }
>  
> +static void pmu_timer_trigger_irq(RISCVCPU *cpu,
> +                                  enum riscv_pmu_event_idx evt_idx)
> +{
> +    uint32_t ctr_idx;
> +    uint32_t ctr_mask;
> +
> +    if (evt_idx != RISCV_PMU_EVENT_HW_CPU_CYCLES &&
> +        evt_idx != RISCV_PMU_EVENT_HW_INSTRUCTIONS) {
> +        return;
> +    }
> +
> +    ctr_mask = riscv_pmu_event_counter_mask(cpu, evt_idx);
> +
> +    while (ctr_mask) {
> +        ctr_idx = ctz32(ctr_mask);
> +        ctr_mask &= ~BIT(ctr_idx);
> +        pmu_timer_trigger_irq_counter(cpu, ctr_idx);
> +    }
> +}
> +
>  /* Timer callback for instret and cycle counter overflow */
>  void riscv_pmu_timer_cb(void *priv)
>  {
> diff --git a/target/riscv/tcg/pmu.h b/target/riscv/tcg/pmu.h
> index 2429c01b776693ebb324ed63fee1feb56c821c21..910091690290cac9f77855f479bb9d90b2762efe 100644
> --- a/target/riscv/tcg/pmu.h
> +++ b/target/riscv/tcg/pmu.h
> @@ -28,8 +28,7 @@ bool riscv_pmu_ctr_monitor_cycles(CPURISCVState *env,
>                                    uint32_t target_ctr);
>  void riscv_pmu_timer_cb(void *priv);
>  void riscv_pmu_init(RISCVCPU *cpu, Error **errp);
> -int riscv_pmu_update_event_map(CPURISCVState *env, uint64_t value,
> -                               uint32_t ctr_idx);
> +void riscv_pmu_rebuild_event_map(CPURISCVState *env);
>  int riscv_pmu_incr_ctr(RISCVCPU *cpu, enum riscv_pmu_event_idx event_idx);
>  void riscv_pmu_generate_fdt_node(void *fdt, uint32_t cmask, char *pmu_name);
>  int riscv_pmu_setup_timer(CPURISCVState *env, uint64_t value,
> diff --git a/tests/tcg/riscv64/sscofpmf-overflow.S b/tests/tcg/riscv64/sscofpmf-overflow.S
> index 97f03037bbfdd44f2288b257afb91499e4534f13..69626831344fe78317c3ca4b743c15ccf11b44b5 100644
> --- a/tests/tcg/riscv64/sscofpmf-overflow.S
> +++ b/tests/tcg/riscv64/sscofpmf-overflow.S
> @@ -6,14 +6,18 @@
>  	.text
>  	.global _start
>  _start:
> -	/* Program hpmcounter3 while no event is selected. */
> +	/* Program counters 3 and 4 while no event is selected. */
>  	csrw	mhpmevent3, zero
>  	li	t0, -256
>  	csrw	mhpmcounter3, t0
> +	csrw	mhpmevent4, zero
> +	li	t0, -512
> +	csrw	mhpmcounter4, t0
>  
> -	/* Start counting retired instructions with overflow enabled. */
> +	/* Count the same event in both counters with overflow enabled. */
>  	li	t0, 2
>  	csrw	mhpmevent3, t0
> +	csrw	mhpmevent4, t0
>  
>  	/* Cross the 64-bit unsigned overflow boundary. */
>  	.rept	1024
> @@ -26,6 +30,10 @@ _start:
>  	srli	t1, t0, 63
>  	xori	t1, t1, 1
>  	or	t4, t4, t1
> +	csrr	t0, mhpmevent4
> +	srli	t1, t0, 63
> +	xori	t1, t1, 1
> +	or	t4, t4, t1
>  
>  	csrr	t0, mip
>  	li	t1, 1 << 13
> @@ -34,12 +42,68 @@ _start:
>  	xori	t0, t0, 1
>  	or	t4, t4, t0
>  
> -	/* The counter wraps and continues counting after overflow. */
> +	/* Both counters wrap and continue counting after overflow. */
>  	csrr	t0, mhpmcounter3
>  	li	t1, -256
>  	sltu	t0, t0, t1
>  	xori	t0, t0, 1
>  	or	t4, t4, t0
> +	csrr	t0, mhpmcounter4
> +	li	t1, -512
> +	sltu	t0, t0, t1
> +	xori	t0, t0, 1
> +	or	t4, t4, t0
> +
> +	/* After selecting write misses, read misses must not increment HPM3. */
> +	csrw	mhpmevent3, zero
> +	csrw	mhpmcounter3, zero
> +	li	t0, 0x10019		/* DTLB read miss */
> +	csrw	mhpmevent3, t0
> +	li	t0, 0x1001b		/* DTLB write miss */
> +	csrw	mhpmevent3, t0
> +	sfence.vma
> +	lla	t2, stale_probe
> +	lw	t3, 0(t2)
> +	csrr	t0, mhpmcounter3
> +	or	t4, t4, t0
> +
> +	/* Both counters must count a DTLB read miss. */
> +	csrw	mhpmevent3, zero
> +	csrw	mhpmevent4, zero
> +	csrw	mhpmcounter3, zero
> +	csrw	mhpmcounter4, zero
> +	li	t0, 0x10019		/* DTLB read miss */
> +	csrw	mhpmevent3, t0
> +	csrw	mhpmevent4, t0
> +	sfence.vma
> +	lla	t2, tlb_probe
> +	lw	t3, 0(t2)
> +	csrr	t0, mhpmcounter3
> +	csrr	t1, mhpmcounter4
> +	sltu	t2, zero, t0
> +	xori	t2, t2, 1
> +	or	t4, t4, t2
> +	sltu	t2, zero, t1
> +	xori	t2, t2, 1
> +	or	t4, t4, t2
> +	xor	t0, t0, t1
> +	sltu	t0, zero, t0
> +	or	t4, t4, t0
> +
> +	/* Disabling HPM3 must leave HPM4 counting the same event. */
> +	csrr	t5, mhpmcounter3
> +	csrr	t6, mhpmcounter4
> +	csrw	mhpmevent3, zero
> +	sfence.vma
> +	lla	t2, tlb_probe2
> +	lw	t3, 0(t2)
> +	csrr	t0, mhpmcounter3
> +	xor	t0, t0, t5
> +	or	t4, t4, t0
> +	csrr	t0, mhpmcounter4
> +	sltu	t0, t6, t0
> +	xori	t0, t0, 1
> +	or	t4, t4, t0
>  
>  	lla	a1, semiargs
>  	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
> @@ -55,6 +119,16 @@ _start:
>  	j	.
>  
>  	.data
> +	/* Give each DTLB probe a separate page. */
> +	.balign	4096
> +stale_probe:
> +	.word	0
> +	.balign	4096
> +tlb_probe:
> +	.word	0
> +	.balign	4096
> +tlb_probe2:
> +	.word	0
>  	.balign	16
>  semiargs:
>  	.space	16
> 
> -- 
> 2.43.0
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 03/14] target/riscv: Use VM-elapsed sources for fixed PMU events
  2026-09-10 14:39 ` [PATCH v2 03/14] target/riscv: Use VM-elapsed sources for fixed PMU events TANG Tiancheng
@ 2026-09-11  3:55   ` Chao Liu
  0 siblings, 0 replies; 20+ messages in thread
From: Chao Liu @ 2026-09-11  3:55 UTC (permalink / raw)
  To: TANG Tiancheng
  Cc: qemu-devel, Zephyr Li, Palmer Dabbelt, Alistair Francis,
	Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé

On Thu, Sep 10, 2026 at 10:39:40PM +0800, TANG Tiancheng wrote:
> Raw host ticks keep advancing while the VM is stopped. Use
> cpus_get_elapsed_ticks() for cycles and non-icount instruction counting,
> and retain icount_get_raw() for instructions under icount. Document that
> cpu_get_ticks() returns its stored value while VM ticks are disabled.
> 
> Under icount, cycles are already virtual nanoseconds. Convert only raw
> instruction counts when scheduling overflow, avoiding a second scaling
> of cycle distances. Add a cycle-overflow regression with icount shift=3.
> 
> Link: https://lists.nongnu.org/archive/html/qemu-devel/2025-10/msg00668.html
> Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
> Reviewed-by: Daniel Henrique Barboza <daniel.barboza@oss.qualcomm.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>

Thanks,
Chao

> ---
>  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/sscofpmf-cycle-overflow.S | 58 +++++++++++++++++++++++++++++
>  tests/tcg/riscv64/system/meson.build        |  7 ++++
>  7 files changed, 103 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/sscofpmf-cycle-overflow.S b/tests/tcg/riscv64/sscofpmf-cycle-overflow.S
> new file mode 100644
> index 0000000000000000000000000000000000000000..846d4651c4ee8f06df83bed85512ab9794552c28
> --- /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	mhpmevent3, zero
> +	li	t0, -4096
> +	csrw	mhpmcounter3, t0
> +	li	t0, 1
> +	csrw	mhpmevent3, t0		/* mhpmevent3: cycles */
> +	csrr	t1, mcycle
> +
> +1:
> +	csrr	t0, mhpmevent3
> +	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
> diff --git a/tests/tcg/riscv64/system/meson.build b/tests/tcg/riscv64/system/meson.build
> index ebe78200fd551b42d3c99ae19ca03803797f803d..668a9a76070b6f16087b08ea84683d883312e951 100644
> --- a/tests/tcg/riscv64/system/meson.build
> +++ b/tests/tcg/riscv64/system/meson.build
> @@ -68,6 +68,13 @@ tests += {
>    },
>  }
>  
> +tests += {
> +  'sscofpmf-cycle-overflow.S': {
> +    'cflags': cflags,
> +    'qemu_args': ['-cpu', 'max', '-icount', 'shift=3', qemu_args],
> +  },
> +}
> +
>  if 'qemu-system-riscv64' in emulators
>    tcg_tests += {
>      'riscv64-softmmu': {
> 
> -- 
> 2.43.0
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 04/14] target/riscv: Preserve MINH on delegated config reads
  2026-09-10 14:39 ` [PATCH v2 04/14] target/riscv: Preserve MINH on delegated config reads TANG Tiancheng
@ 2026-09-11  3:56   ` Chao Liu
  0 siblings, 0 replies; 20+ messages in thread
From: Chao Liu @ 2026-09-11  3:56 UTC (permalink / raw)
  To: TANG Tiancheng
  Cc: qemu-devel, Zephyr Li, Palmer Dabbelt, Alistair Francis,
	Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé

On Thu, Sep 10, 2026 at 10:39:41PM +0800, TANG Tiancheng wrote:
> Smcdeleg requires MINH to read as zero through sireg*. The RV64 callback
> masks it by modifying the machine register; the RV32 high-half callback
> does not mask it.
> 
> Return a masked copy without changing mcyclecfg or minstretcfg. Test both
> configuration registers on RV32 and RV64.
> 
> Fixes: d9fa41e10156 ("target/riscv: Bugfix make bit 62 read-only 0 for sireg* cfg CSR read")
> Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>

Thanks,
Chao

> ---
>  target/riscv/tcg/csr.c                 |  8 ++--
>  tests/tcg/riscv32/smcdeleg-minh-rv32.S | 78 ++++++++++++++++++++++++++++++++++
>  tests/tcg/riscv32/system/meson.build   |  7 +++
>  tests/tcg/riscv64/smcdeleg-minh.S      | 78 ++++++++++++++++++++++++++++++++++
>  tests/tcg/riscv64/system/meson.build   |  7 +++
>  5 files changed, 174 insertions(+), 4 deletions(-)
> 
> diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
> index 60caee32dc0cf5b6a8492e0cf8ff15f71acc2087..57030724f85a897e21e5082b9857411b50f932ac 100644
> --- a/target/riscv/tcg/csr.c
> +++ b/target/riscv/tcg/csr.c
> @@ -1607,7 +1607,7 @@ static int rmw_cd_ctr_cfg(CPURISCVState *env, int cfg_index, target_ulong *val,
>              wr_mask &= ~MCYCLECFG_BIT_MINH;
>              env->mcyclecfg = (new_val & wr_mask) | (env->mcyclecfg & ~wr_mask);
>          } else {
> -            *val = env->mcyclecfg &= ~MHPMEVENT_BIT_MINH;
> +            *val = env->mcyclecfg & ~MCYCLECFG_BIT_MINH;
>          }
>          break;
>      case 2:             /* INSTRETCFG */
> @@ -1616,7 +1616,7 @@ static int rmw_cd_ctr_cfg(CPURISCVState *env, int cfg_index, target_ulong *val,
>              env->minstretcfg = (new_val & wr_mask) |
>                                 (env->minstretcfg & ~wr_mask);
>          } else {
> -            *val = env->minstretcfg &= ~MHPMEVENT_BIT_MINH;
> +            *val = env->minstretcfg & ~MINSTRETCFG_BIT_MINH;
>          }
>          break;
>      default:
> @@ -1642,7 +1642,7 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
>              cfgh = (new_val & wr_mask) | (cfgh & ~wr_mask);
>              env->mcyclecfg = deposit64(env->mcyclecfg, 32, 32, cfgh);
>          } else {
> -            *val = cfgh;
> +            *val = cfgh & ~MCYCLECFGH_BIT_MINH;
>          }
>          break;
>      case 2:          /* INSTRETCFGH */
> @@ -1652,7 +1652,7 @@ static int rmw_cd_ctr_cfgh(CPURISCVState *env, int cfg_index, target_ulong *val,
>              cfgh = (new_val & wr_mask) | (cfgh & ~wr_mask);
>              env->minstretcfg = deposit64(env->minstretcfg, 32, 32, cfgh);
>          } else {
> -            *val = cfgh;
> +            *val = cfgh & ~MINSTRETCFGH_BIT_MINH;
>          }
>          break;
>      default:
> diff --git a/tests/tcg/riscv32/smcdeleg-minh-rv32.S b/tests/tcg/riscv32/smcdeleg-minh-rv32.S
> new file mode 100644
> index 0000000000000000000000000000000000000000..db467b95cf1441e3440f59097401cf6956d294d3
> --- /dev/null
> +++ b/tests/tcg/riscv32/smcdeleg-minh-rv32.S
> @@ -0,0 +1,78 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +/* CSR numbers for older assemblers. */
> +#define CSR_SISELECT         0x150
> +#define CSR_SIREG5           0x156
> +#define CSR_MENVCFGH         0x31a
> +#define CSR_MCYCLECFGH       0x721
> +#define CSR_MINSTRETCFGH     0x722
> +
> +	.option	norvc
> +	.option	norelax
> +
> +	.text
> +	.global	_start
> +_start:
> +	/*
> +	 * Failure bits:
> +	 * 0: delegated cyclecfgh exposes MINH
> +	 * 1: reading delegated cyclecfgh clears mcyclecfgh.MINH
> +	 * 2: delegated instretcfgh exposes MINH
> +	 * 3: reading delegated instretcfgh clears minstretcfgh.MINH
> +	 */
> +	li	t4, 0
> +	li	t0, 1
> +	slli	t0, t0, 30		/* MINH in the high half */
> +	csrw	CSR_MCYCLECFGH, t0
> +	csrw	CSR_MINSTRETCFGH, t0
> +	li	t1, 1
> +	slli	t1, t1, 28		/* menvcfgh.CDE */
> +	csrw	CSR_MENVCFGH, t1
> +	li	t1, 5			/* Delegate cycle and instret. */
> +	csrw	mcounteren, t1
> +
> +	/* Check the delegated view, then the underlying machine register. */
> +	li	t1, 0x40		/* siselect: cycle */
> +	csrw	CSR_SISELECT, t1
> +	csrr	t1, CSR_SIREG5
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	or	t4, t4, t1
> +	csrr	t1, CSR_MCYCLECFGH
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	xori	t1, t1, 1
> +	slli	t1, t1, 1
> +	or	t4, t4, t1
> +
> +	li	t1, 0x42		/* siselect: instret */
> +	csrw	CSR_SISELECT, t1
> +	csrr	t1, CSR_SIREG5
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	slli	t1, t1, 2
> +	or	t4, t4, t1
> +	csrr	t1, CSR_MINSTRETCFGH
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	xori	t1, t1, 1
> +	slli	t1, t1, 3
> +	or	t4, t4, t1
> +
> +	lla	a1, semiargs
> +	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
> +	sw	t0, 0(a1)
> +	sw	t4, 4(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	8
> diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
> index 16f9a06c9485ed75b3d127d6b9d090f5eaad4486..cfe2d854b729a8dfdbce2373e73b91b470db6b4e 100644
> --- a/tests/tcg/riscv32/system/meson.build
> +++ b/tests/tcg/riscv32/system/meson.build
> @@ -22,6 +22,13 @@ tests += {
>    },
>  }
>  
> +tests += {
> +  'smcdeleg-minh-rv32.S': {
> +    'cflags': cflags,
> +    'qemu_args': ['-cpu', 'max', qemu_args],
> +  },
> +}
> +
>  if 'qemu-system-riscv32' in emulators
>    tcg_tests += {
>      'riscv32-softmmu': {
> diff --git a/tests/tcg/riscv64/smcdeleg-minh.S b/tests/tcg/riscv64/smcdeleg-minh.S
> new file mode 100644
> index 0000000000000000000000000000000000000000..38d3c30f0afaa416a46322f2b892e5969172a60f
> --- /dev/null
> +++ b/tests/tcg/riscv64/smcdeleg-minh.S
> @@ -0,0 +1,78 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +/* CSR numbers for older assemblers. */
> +#define CSR_SISELECT         0x150
> +#define CSR_SIREG2           0x152
> +#define CSR_MENVCFG          0x30a
> +#define CSR_MCYCLECFG        0x321
> +#define CSR_MINSTRETCFG      0x322
> +
> +	.option	norvc
> +	.option	norelax
> +
> +	.text
> +	.global	_start
> +_start:
> +	/*
> +	 * Failure bits:
> +	 * 0: delegated cyclecfg exposes MINH
> +	 * 1: reading delegated cyclecfg clears mcyclecfg.MINH
> +	 * 2: delegated instretcfg exposes MINH
> +	 * 3: reading delegated instretcfg clears minstretcfg.MINH
> +	 */
> +	li	t4, 0
> +	li	t0, 1
> +	slli	t0, t0, 62		/* MINH */
> +	csrw	CSR_MCYCLECFG, t0
> +	csrw	CSR_MINSTRETCFG, t0
> +	li	t1, 1
> +	slli	t1, t1, 60		/* menvcfg.CDE */
> +	csrw	CSR_MENVCFG, t1
> +	li	t1, 5			/* Delegate cycle and instret. */
> +	csrw	mcounteren, t1
> +
> +	/* Check the delegated view, then the underlying machine register. */
> +	li	t1, 0x40		/* siselect: cycle */
> +	csrw	CSR_SISELECT, t1
> +	csrr	t1, CSR_SIREG2
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	or	t4, t4, t1
> +	csrr	t1, CSR_MCYCLECFG
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	xori	t1, t1, 1
> +	slli	t1, t1, 1
> +	or	t4, t4, t1
> +
> +	li	t1, 0x42		/* siselect: instret */
> +	csrw	CSR_SISELECT, t1
> +	csrr	t1, CSR_SIREG2
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	slli	t1, t1, 2
> +	or	t4, t4, t1
> +	csrr	t1, CSR_MINSTRETCFG
> +	and	t1, t1, t0
> +	sltu	t1, zero, t1
> +	xori	t1, t1, 1
> +	slli	t1, t1, 3
> +	or	t4, t4, t1
> +
> +	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 668a9a76070b6f16087b08ea84683d883312e951..5d91381c62d77ae7e37c129112d6367f692df660 100644
> --- a/tests/tcg/riscv64/system/meson.build
> +++ b/tests/tcg/riscv64/system/meson.build
> @@ -75,6 +75,13 @@ tests += {
>    },
>  }
>  
> +tests += {
> +  'smcdeleg-minh.S': {
> +    'cflags': cflags,
> +    'qemu_args': ['-cpu', 'max', qemu_args],
> +  },
> +}
> +
>  if 'qemu-system-riscv64' in emulators
>    tcg_tests += {
>      'riscv64-softmmu': {
> 
> -- 
> 2.43.0
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v2 05/14] target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes
  2026-09-10 14:39 ` [PATCH v2 05/14] target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes TANG Tiancheng
@ 2026-09-11  4:43   ` Chao Liu
  0 siblings, 0 replies; 20+ messages in thread
From: Chao Liu @ 2026-09-11  4:43 UTC (permalink / raw)
  To: TANG Tiancheng
  Cc: qemu-devel, Zephyr Li, Palmer Dabbelt, Alistair Francis,
	Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei, qemu-riscv,
	Richard Henderson, Paolo Bonzini, Philippe Mathieu-Daudé

On Thu, Sep 10, 2026 at 10:39:42PM +0800, TANG Tiancheng wrote:
> RV32 write_minstretcfg() replaces the full 64-bit register, clearing
> minstretcfgh and its privilege-inhibit bits.
> 
> Replace only bits 31:0, as write_mcyclecfg() does. Test that a low-half
> write preserves both set and clear xINH bits in minstretcfgh.
> 
> Fixes: b54a84c15e38 ("target/riscv: Add cycle & instret privilege mode filtering support")
> Signed-off-by: TANG Tiancheng <lyndra@linux.alibaba.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>

Thanks,
Chao

> ---
>  target/riscv/tcg/csr.c                   |  2 +-
>  tests/tcg/riscv32/pmu-minstretcfg-rv32.S | 52 ++++++++++++++++++++++++++++++++
>  tests/tcg/riscv32/system/meson.build     |  7 +++++
>  3 files changed, 60 insertions(+), 1 deletion(-)
> 
> diff --git a/target/riscv/tcg/csr.c b/target/riscv/tcg/csr.c
> index 57030724f85a897e21e5082b9857411b50f932ac..f9f43a9c12e1afdbdf6c7202c167988389963c10 100644
> --- a/target/riscv/tcg/csr.c
> +++ b/target/riscv/tcg/csr.c
> @@ -1167,7 +1167,7 @@ static RISCVException write_minstretcfg(CPURISCVState *env, int csrno,
>      uint64_t inh_avail_mask;
>  
>      if (riscv_cpu_mxl(env) == MXL_RV32) {
> -        env->minstretcfg = val;
> +        env->minstretcfg = deposit64(env->minstretcfg, 0, 32, val);
>      } else {
>          inh_avail_mask = ~MHPMEVENT_FILTER_MASK | MINSTRETCFG_BIT_MINH;
>          inh_avail_mask |= riscv_has_ext(env, RVU) ? MINSTRETCFG_BIT_UINH : 0;
> diff --git a/tests/tcg/riscv32/pmu-minstretcfg-rv32.S b/tests/tcg/riscv32/pmu-minstretcfg-rv32.S
> new file mode 100644
> index 0000000000000000000000000000000000000000..59ee516cea9d09d0945033a77e788e4387cd684f
> --- /dev/null
> +++ b/tests/tcg/riscv32/pmu-minstretcfg-rv32.S
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +/* CSR numbers for older assemblers. */
> +#define CSR_MINSTRETCFG      0x322
> +#define CSR_MINSTRETCFGH     0x722
> +
> +/* RV32 writes to minstretcfg must preserve minstretcfgh. */
> +
> +	.option	norvc
> +	.option	norelax
> +
> +	.text
> +	.global _start
> +_start:
> +	/*
> +	 * Use complementary patterns to check both set and clear xINH bits.
> +	 * All fields in the low half are WPRI, so write zero there.
> +	 * Exit status 1 or 2 identifies the pattern that was not preserved.
> +	 */
> +	li	t4, 1
> +	li	t0, 0x54000000		/* MINH, UINH, VUINH */
> +	csrw	CSR_MINSTRETCFGH, t0
> +	csrw	CSR_MINSTRETCFG, zero
> +	csrr	t1, CSR_MINSTRETCFGH
> +	bne	t0, t1, exit
> +
> +	li	t4, 2
> +	li	t0, 0x28000000		/* SINH, VSINH */
> +	csrw	CSR_MINSTRETCFGH, t0
> +	csrw	CSR_MINSTRETCFG, zero
> +	csrr	t1, CSR_MINSTRETCFGH
> +	bne	t0, t1, exit
> +	li	t4, 0
> +
> +exit:
> +	lla	a1, semiargs
> +	li	t0, 0x20026		/* ADP_Stopped_ApplicationExit */
> +	sw	t0, 0(a1)
> +	sw	t4, 4(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	8
> diff --git a/tests/tcg/riscv32/system/meson.build b/tests/tcg/riscv32/system/meson.build
> index cfe2d854b729a8dfdbce2373e73b91b470db6b4e..5f417c0c51e17840072104812f5854dfb65d1d06 100644
> --- a/tests/tcg/riscv32/system/meson.build
> +++ b/tests/tcg/riscv32/system/meson.build
> @@ -29,6 +29,13 @@ tests += {
>    },
>  }
>  
> +tests += {
> +  'pmu-minstretcfg-rv32.S': {
> +    'cflags': cflags,
> +    'qemu_args': ['-cpu', 'max', qemu_args],
> +  },
> +}
> +
>  if 'qemu-system-riscv32' in emulators
>    tcg_tests += {
>      'riscv32-softmmu': {
> 
> -- 
> 2.43.0
> 


^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-09-11  4:44 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 14:39 [PATCH v2 00/14] RISC-V TCG PMU correctness fixes TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 01/14] target/riscv: Preserve PMU state across event selector writes TANG Tiancheng
2026-09-11  3:47   ` Chao Liu
2026-09-10 14:39 ` [PATCH v2 02/14] target/riscv: Support multiple counters per PMU event TANG Tiancheng
2026-09-11  3:54   ` Chao Liu
2026-09-10 14:39 ` [PATCH v2 03/14] target/riscv: Use VM-elapsed sources for fixed PMU events TANG Tiancheng
2026-09-11  3:55   ` Chao Liu
2026-09-10 14:39 ` [PATCH v2 04/14] target/riscv: Preserve MINH on delegated config reads TANG Tiancheng
2026-09-11  3:56   ` Chao Liu
2026-09-10 14:39 ` [PATCH v2 05/14] target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes TANG Tiancheng
2026-09-11  4:43   ` Chao Liu
2026-09-10 14:39 ` [PATCH v2 06/14] target/riscv: Fix RV32 accesses to delegated PMU registers TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 07/14] target/riscv: Preserve fixed counters across PMU state changes TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 08/14] target/riscv: Require Sscofpmf for non-fixed event overflow TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 09/14] target/riscv: Rebuild fixed-event PMU overflow deadlines TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 10/14] target/riscv: Apply minstret exception accounting to HPM counters TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 11/14] target/riscv: Process PMU timer expiry on the owner vCPU TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 12/14] target/riscv: Migrate fixed PMU counter state TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 13/14] target/riscv: Clear virtualization mode on reset TANG Tiancheng
2026-09-10 14:39 ` [PATCH v2 14/14] target/riscv: Preserve fixed PMU state across reset TANG Tiancheng

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.