Skip to contentSkip to conversation

RISC-V TCG PMU correctness fixes

PATCH In review TANG Tiancheng 2 participants 17 messages Oct 10 – Nov 11

Subsystems (2)
lore
Share & tools
lore
#0 lore raw

v3 cover RISC-V TCG PMU correctness fixes

This series fixes RISC-V TCG PMU accounting, overflow notification, and migration. It is based on v3 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/[email protected]/
Based-on: <[email protected]>

---
Changes in v3:
- Rebase on master f9587d4045c6 and Zephyr Li's RV32 fix v3.
- Replace "Earlier expiries" with "Timer expirations before the flag
is cleared" in patch 11's commit message.
- Add a Fixes trailer to the virtualization-reset fix (patch 13).
- Link to v2: https://lore.kernel.org/qemu-devel/20260910-riscv-pmu-correctness-v2-0-5da5159a0c64@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: f9587d4045c67cd0d8d8bdcd5d0bb5b6b395b63c
change-id: 20260827-riscv-pmu-correctness-fa880fd9c48d
prerequisite-message-id: <[email protected]>
prerequisite-patch-id: fc09febb37e6eff15ef860aa49a5d4f85a0f2ee9

Best regards,

Signature · 1 line
TANG Tiancheng <[email protected]>
#1 lore raw

PATCH v3 1/14 target/riscv: Preserve PMU state across event selector writes

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")
---
 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(-)
target/riscv/tcg/csr.c +52 -21
index cab5647e7c6fd083969e227803671914207a5682..22a821341b387aaef748288ec8caa6d3f021727d 100644
@@ -1212,23 +1212,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;
@@ -1240,8 +1275,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;
 }
@@ -1249,9 +1283,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;
 }
@@ -1259,7 +1293,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);
 
@@ -1270,10 +1304,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;
 }
@@ -1515,11 +1548,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;
@@ -1534,8 +1567,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;
     }
@@ -1543,11 +1575,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) {
@@ -1563,8 +1595,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;
     }
tests/tcg/riscv64/sscofpmf-overflow.S new file +60 -0

60 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +7 -0
index c2d7d098871d3301d64fb1e3d69e78792a1a6c07..b798b8ea38e8151f7a832ae3d582cc3a2ab1e98e 100644
@@ -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': {
Signature · 1 line
2.43.0
#2 lore raw

PATCH v3 2/14 target/riscv: Support multiple counters per PMU event

In reply to 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")
---
 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(-)
target/riscv/machine.c +6 -0
index bf203bffcefb32710ed0f2af4d4f4595e122d1d9..b0ff2fc7f2ac10fab1f2ff845a953649091e1f43 100644
@@ -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;
 }
 
target/riscv/tcg/csr.c +1 -1
index 22a821341b387aaef748288ec8caa6d3f021727d..d2f15cfef0e63eaf4ca96d193983d1cb3d272ecc 100644
@@ -1244,7 +1244,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) ||
target/riscv/tcg/pmu.c +91 -91

266 lines not on this page · raw message

target/riscv/tcg/pmu.h +1 -2
index 2429c01b776693ebb324ed63fee1feb56c821c21..910091690290cac9f77855f479bb9d90b2762efe 100644
@@ -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,
tests/tcg/riscv64/sscofpmf-overflow.S +77 -3

115 lines not on this page · raw message

Signature · 1 line
2.43.0
#3 lore raw

PATCH v3 3/14 target/riscv: Use VM-elapsed sources for fixed PMU events

In reply to 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.

---
 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(-)
system/cpu-timers.c +2 -2
index 9919b46230f1caf8be1a1b6ef00acd94678437ce..0415636aff3f774f0de61fdac3969f5b45ae6993 100644
@@ -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)
system/cpus.c +3 -3
index e11a5aab6a696962d94ad30ac38acd8867b1bf88..f61639ae78277fd90cbddb0b9f75b134e3cc1a17 100644
@@ -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)
 {
target/riscv/tcg/csr.c +1 -7
index d2f15cfef0e63eaf4ca96d193983d1cb3d272ecc..f2fe7700fdc64600eff1bb0aef28a0ca212d687c 100644
@@ -1330,13 +1330,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. */
target/riscv/tcg/pmu.c +31 -21

92 lines not on this page · raw message

target/riscv/tcg/pmu.h +1 -0
index 910091690290cac9f77855f479bb9d90b2762efe..339a4b3ac09c4a91cddd9250824284203b16b4fa 100644
@@ -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);
tests/tcg/riscv64/sscofpmf-cycle-overflow.S new file +58 -0

58 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +7 -0
index b798b8ea38e8151f7a832ae3d582cc3a2ab1e98e..3108e6595d4e91e6a941e006206992a41ddbe436 100644
@@ -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': {
Signature · 1 line
2.43.0
#4 lore raw

PATCH v3 4/14 target/riscv: Preserve MINH on delegated config reads

In reply to 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")
---
 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(-)
target/riscv/tcg/csr.c +4 -4
index f2fe7700fdc64600eff1bb0aef28a0ca212d687c..32c8282dfcb3ec578150f107ccc8bd753085a050 100644
@@ -1610,7 +1610,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 */
@@ -1619,7 +1619,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:
@@ -1645,7 +1645,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 */
@@ -1655,7 +1655,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:
tests/tcg/riscv32/smcdeleg-minh-rv32.S new file +78 -0

78 lines not on this page · raw message

tests/tcg/riscv32/system/meson.build +7 -0
index f8e55ad3d4e6b64dc4a59e048b9cce71fdce120c..09e2c4fc8c6f356dc27ae9299f3231e2e3356891 100644
@@ -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': {
tests/tcg/riscv64/smcdeleg-minh.S new file +78 -0

78 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +7 -0
index 3108e6595d4e91e6a941e006206992a41ddbe436..6af497a1a41debe58dfa329060510bd896c88ac7 100644
@@ -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': {
Signature · 1 line
2.43.0
#5 lore raw

PATCH v3 5/14 target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes

In reply to 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")
---
 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(-)
target/riscv/tcg/csr.c +1 -1
index 32c8282dfcb3ec578150f107ccc8bd753085a050..504e9e145bd7fbd85904e6d3a0b5279328ed4e19 100644
@@ -1170,7 +1170,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;
tests/tcg/riscv32/pmu-minstretcfg-rv32.S new file +52 -0

52 lines not on this page · raw message

tests/tcg/riscv32/system/meson.build +7 -0
index 09e2c4fc8c6f356dc27ae9299f3231e2e3356891..d1916abaacd928dc708437b75ac974fb9cfdca85 100644
@@ -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': {
Signature · 1 line
2.43.0
#6 lore raw

PATCH v3 6/14 target/riscv: Fix RV32 accesses to delegated PMU registers

In reply to 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")
---
 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(-)
target/riscv/tcg/csr.c +20 -20
index 504e9e145bd7fbd85904e6d3a0b5279328ed4e19..e563a77a567fa3bbca0bc79375099cfab0e43ac7 100644
@@ -1360,10 +1360,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;
 
@@ -1421,7 +1421,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,
@@ -1433,10 +1433,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;
@@ -1485,7 +1486,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,
@@ -1501,21 +1502,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;
     }
@@ -1527,12 +1530,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 {
@@ -1547,8 +1550,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;
     }
 
@@ -1576,7 +1580,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;
     }
 
@@ -1633,10 +1637,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);
@@ -2812,9 +2812,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;
     }
target/riscv/tcg/pmu.c +4 -2
index ea0ffe41258d4dbef9dc952655e6301c14ba1d23..54fff2ba49c1034d5fa6db1c7b19ff59003cd1e1 100644
@@ -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);
     }
 
target/riscv/tcg/pmu.h +2 -1
index 339a4b3ac09c4a91cddd9250824284203b16b4fa..bf2e8373474d471d914f8801c55d2f6ffbb5cdd3 100644
@@ -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 */
tests/tcg/riscv32/smcdeleg-rv32.S new file +115 -0

115 lines not on this page · raw message

tests/tcg/riscv32/system/meson.build +7 -0
index d1916abaacd928dc708437b75ac974fb9cfdca85..f1506d06967d64ce3e908f35ee82c2442b2556fb 100644
@@ -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': {
tests/tcg/riscv64/smcdeleg-sxl32.S new file +233 -0

233 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +31 -0
index 6af497a1a41debe58dfa329060510bd896c88ac7..679ccf414208b0c84b0de1e7beea7201cb4d6b70 100644
@@ -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,
Signature · 1 line
2.43.0
#7 lore raw

PATCH v3 7/14 target/riscv: Preserve fixed counters across PMU state changes

In reply to 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.

---
 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(-)
target/riscv/cpu.h +7 -1
index 8c8ffe94cd89a0f5fad806ce16ff539eb4110764..a660e16cf6e38bd5c287ed2013539d51efc2848f 100644
@@ -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];
@@ -468,7 +474,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;
target/riscv/tcg/csr.c +44 -199

384 lines not on this page · raw message

target/riscv/tcg/pmu.c +264 -45

361 lines not on this page · raw message

target/riscv/tcg/pmu.h +17 -1
index bf2e8373474d471d914f8801c55d2f6ffbb5cdd3..1494fbc21f53137a90c1338f6ca8e3c3e750276a 100644
@@ -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);
tests/tcg/riscv32/pmu-fixed-rv32.S new file +90 -0

90 lines not on this page · raw message

tests/tcg/riscv32/sscofpmf-event-rv32.S new file +99 -0

99 lines not on this page · raw message

tests/tcg/riscv32/system/meson.build +14 -0
index f1506d06967d64ce3e908f35ee82c2442b2556fb..629278006c6a979682b98e06f1a9243b6dbc556b 100644
@@ -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': {
tests/tcg/riscv64/pmu-cycle-controls.S new file +80 -0

80 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +7 -0
index 679ccf414208b0c84b0de1e7beea7201cb4d6b70..6f13ee95b798250d7b4f8f82344aa550c794888f 100644
@@ -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': {
Signature · 1 line
2.43.0
#8 lore raw

PATCH v3 8/14 target/riscv: Require Sscofpmf for non-fixed event overflow

In reply to 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")
---
 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(-)
target/riscv/tcg/pmu.c +1 -5
index 16942e53489cd5a2d2dd055fdffef07d04d59465..2f600c5a0fc2d7ba380ef34f89af6366e3e27884 100644
@@ -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++;
         }
tests/tcg/riscv64/sscofpmf-event-overflow.S new file +103 -0

103 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +15 -0
index 6f13ee95b798250d7b4f8f82344aa550c794888f..3ad251586d11a728ea989859889751bd58198dd2 100644
@@ -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,
Signature · 1 line
2.43.0
#9 lore raw

PATCH v3 9/14 target/riscv: Rebuild fixed-event PMU overflow deadlines

In reply to 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.

---
 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(-)
target/riscv/cpu.h +2 -2
index a660e16cf6e38bd5c287ed2013539d51efc2848f..1ab6ae6937aeca524c4635eae6c000f4a6d1ac5a 100644
@@ -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 {
@@ -586,6 +584,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 */
target/riscv/tcg/cpu_helper.c +2 -0
index 07d92226527d85da93b8810e526d044e64fa4e3d..89751cdbf29f5bbd46d0d6b8c9d23eac5e3accac 100644
@@ -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);
 }
 
 /*
target/riscv/tcg/pmu.c +97 -142

326 lines not on this page · raw message

target/riscv/tcg/pmu.h +1 -2
index 1494fbc21f53137a90c1338f6ca8e3c3e750276a..d9238ae680f5e67031511db4f9afc2884212c5c2 100644
@@ -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);
target/riscv/tcg/tcg-cpu.c +10 -0
index b68160af8307c1e46d3f200c5313823d240eb7b3..cdcb94f3bcaf0526512f1994e9f7127209e1adcb 100644
@@ -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,
tests/tcg/riscv64/pmu-cycle-controls.S +27 -0
index fdd14755f4d196dc2a3ec6c8b8540adc40b3a253..474d46d61baa2c3ec55a5a40d7bdb7ddd3dd7f2f 100644
@@ -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)
Signature · 1 line
2.43.0
#10 lore raw

PATCH v3 10/14 target/riscv: Apply minstret exception accounting to HPM counters

In reply to 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")
---
 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(-)
target/riscv/tcg/pmu.c +28 -7
index f88f6ae671d877ed874d22c9d4b840edb6f433a5..08298010b6d06f5792fa14ff81fe2f7a28c6476f 100644
@@ -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)
tests/tcg/riscv64/pmu-lpad.S new file +72 -0

72 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +7 -0
index 3ad251586d11a728ea989859889751bd58198dd2..c24468ab560ccf525f7ded98c0dd53a67e0833e2 100644
@@ -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': {
tests/tcg/riscv64/test-minstret-ecall.S +26 -0
index ab268f7f22985820f19bde353215385608643f3a..b1857543d488df984b923ad1c135edb35cb948c7 100644
@@ -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
Signature · 1 line
2.43.0
#11 lore raw

PATCH v3 11/14 target/riscv: Process PMU timer expiry on the owner vCPU

In reply to 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. Timer expirations before the flag is cleared are covered by that check; the first expiration afterward queues another check.

---
 target/riscv/cpu.h     |  1 +
 target/riscv/tcg/pmu.c | 18 ++++++++++++++++--
 2 files changed, 17 insertions(+), 2 deletions(-)
target/riscv/cpu.h +1 -0
index 1ab6ae6937aeca524c4635eae6c000f4a6d1ac5a..abc9180e35dae9f713652038f3e677d520e2ef50 100644
@@ -586,6 +586,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 */
target/riscv/tcg/pmu.c +16 -2
index 08298010b6d06f5792fa14ff81fe2f7a28c6476f..6286552a4ebf614df0252f84ddfadbc25d8d2258 100644
@@ -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)) {
Signature · 1 line
2.43.0
#12 lore raw

PATCH v3 12/14 target/riscv: Migrate fixed PMU counter state

In reply to 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

---
 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(-)
target/riscv/cpu.h +1 -0
index abc9180e35dae9f713652038f3e677d520e2ef50..67ecdc51d32b0cea7808a13c0f138a9deac31f38 100644
@@ -587,6 +587,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 */
target/riscv/machine.c +81 -7

131 lines not on this page · raw message

target/riscv/tcg/pmu.c +79 -0

85 lines not on this page · raw message

target/riscv/tcg/pmu.h +2 -0
index d9238ae680f5e67031511db4f9afc2884212c5c2..1cfe6acf55b5468f5c00c4a136ece88981384341 100644
@@ -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);
Signature · 1 line
2.43.0
#13 lore raw

PATCH v3 13/14 target/riscv: Clear virtualization mode on reset

In reply to 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.

Fixes ef6bb7b62682 ("target/riscv: Add the virtulisation mode")
---
 target/riscv/cpu.c | 1 +
 1 file changed, 1 insertion(+)
target/riscv/cpu.c +1 -0
index da4c1a090b9b74c5b11dac94987fe573cc1aed5c..34422d0204e7c1532c3424626c70d33f805833d5 100644
@@ -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) {
         /*
Signature · 1 line
2.43.0
#14 lore raw

PATCH v3 14/14 target/riscv: Preserve fixed PMU state across reset

In reply to 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.

---
 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(+)
target/riscv/cpu.c +16 -0
index 34422d0204e7c1532c3424626c70d33f805833d5..2a05c91bbe09313c22e4263d6dfa16bb5aca11d5 100644
@@ -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);
@@ -1095,6 +1108,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);
     }
target/riscv/tcg/pmu.c +11 -0
index df99b572a4c16cb1ac65c2f7cde35c6f8349e681..1d692a0a8e3f7759e033214d579103627d070b5b 100644
@@ -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)
target/riscv/tcg/pmu.h +1 -0
index 1cfe6acf55b5468f5c00c4a136ece88981384341..fac84dbb1a8a6c637f7241ca27910e5e212223e9 100644
@@ -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,
tests/tcg/riscv64/pmu-reset-vs.S new file +105 -0

105 lines not on this page · raw message

tests/tcg/riscv64/system/meson.build +7 -0
index c24468ab560ccf525f7ded98c0dd53a67e0833e2..d89d72ac49a57e87bf6e8ae905c3af6f3550f77c 100644
@@ -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': {
Signature · 1 line
2.43.0
#15 lore raw
In reply to TANG Tiancheng

Hi Alistair,

Thanks for queuing Harshit's fix [1]. Patch 9 of this series reworks the PMU overflow timer and removes the affected read path. The patches carry Reviewed-by tags from Daniel and Chao.

Would you like me to rebase this series on riscv-to-apply.next and send v4?

Thanks,
Tiancheng

[1] https://lore.kernel.org/qemu-devel/[email protected]/

Quoted 98 lines · TANG Tiancheng

This series fixes RISC-V TCG PMU accounting, overflow notification, and migration. It is based on v3 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/[email protected]/
Based-on: <[email protected]>

---
Changes in v3:
- Rebase on master f9587d4045c6 and Zephyr Li's RV32 fix v3.
- Replace "Earlier expiries" with "Timer expirations before the flag
is cleared" in patch 11's commit message.
- Add a Fixes trailer to the virtualization-reset fix (patch 13).
- Link to v2: https://lore.kernel.org/qemu-devel/20260910-riscv-pmu-correctness-v2-0-5da5159a0c64@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: f9587d4045c67cd0d8d8bdcd5d0bb5b6b395b63c
change-id: 20260827-riscv-pmu-correctness-fa880fd9c48d
prerequisite-message-id: <[email protected]>
prerequisite-patch-id: fc09febb37e6eff15ef860aa49a5d4f85a0f2ee9

Best regards,

#16 lore raw
Quoted 9 lines · TianCheng TANG

Hi Alistair,

Thanks for queuing Harshit's fix [1]. Patch 9 of this series reworks the
PMU overflow timer and removes the affected read path. The patches carry Reviewed-by tags from Daniel and Chao.

Would you like me to rebase this series on riscv-to-apply.next and send v4?

Yes please!

Alistair

Quoted 131 lines

Thanks,
Tiancheng

[1]
https://lore.kernel.org/qemu-devel/[email protected]/

Quoted 122 lines · TANG Tiancheng

This series fixes RISC-V TCG PMU accounting, overflow notification, and
migration. It is based on v3 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/[email protected]/
Based-on: <[email protected]>

---
Changes in v3:
- Rebase on master f9587d4045c6 and Zephyr Li's RV32 fix v3.
- Replace "Earlier expiries" with "Timer expirations before the flag
   is cleared" in patch 11's commit message.
- Add a Fixes trailer to the virtualization-reset fix (patch 13).
- Link to v2:
https://lore.kernel.org/qemu-devel/20260910-riscv-pmu-correctness-v2-0-5da5159a0c64@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: f9587d4045c67cd0d8d8bdcd5d0bb5b6b395b63c
change-id: 20260827-riscv-pmu-correctness-fa880fd9c48d prerequisite-message-id:
<[email protected]>
prerequisite-patch-id: fc09febb37e6eff15ef860aa49a5d4f85a0f2ee9

Best regards,