RISC-V TCG PMU correctness fixes
Subsystems (2)
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]>
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.
--- 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
@@ -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
@@ -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
PATCH v3 2/14 target/riscv: Support multiple counters per PMU event
In reply to TANG TianchengThe 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.
--- 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
@@ -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
@@ -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
@@ -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
PATCH v3 3/14 target/riscv: Use VM-elapsed sources for fixed PMU events
In reply to TANG TianchengRaw 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v3 4/14 target/riscv: Preserve MINH on delegated config reads
In reply to TANG TianchengSmcdeleg 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.
--- 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
@@ -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
@@ -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
@@ -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
PATCH v3 5/14 target/riscv: Preserve minstretcfgh on RV32 minstretcfg writes
In reply to TANG TianchengRV32 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.
--- 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
@@ -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
@@ -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
PATCH v3 6/14 target/riscv: Fix RV32 accesses to delegated PMU registers
In reply to TANG TianchengDelegated 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.
--- 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v3 7/14 target/riscv: Preserve fixed counters across PMU state changes
In reply to TANG Tianchengmcycle/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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v3 8/14 target/riscv: Require Sscofpmf for non-fixed event overflow
In reply to TANG Tianchengriscv_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.
--- 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
@@ -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
@@ -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
PATCH v3 9/14 target/riscv: Rebuild fixed-event PMU overflow deadlines
In reply to TANG Tianchengtimer_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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v3 10/14 target/riscv: Apply minstret exception accounting to HPM counters
In reply to TANG TianchengWith 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.
--- 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
@@ -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
@@ -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
@@ -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
PATCH v3 11/14 target/riscv: Process PMU timer expiry on the owner vCPU
In reply to TANG TianchengThe 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
@@ -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
@@ -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
PATCH v3 12/14 target/riscv: Migrate fixed PMU counter state
In reply to TANG TianchengMigration 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
@@ -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
@@ -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
PATCH v3 13/14 target/riscv: Clear virtualization mode on reset
In reply to TANG TianchengReset 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.
--- target/riscv/cpu.c | 1 + 1 file changed, 1 insertion(+)
target/riscv/cpu.c +1 -0
@@ -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
PATCH v3 14/14 target/riscv: Preserve fixed PMU state across reset
In reply to TANG TianchengPrivilege 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
@@ -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
@@ -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
@@ -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
@@ -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
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]>Signed-off-by TANG Tiancheng <[email protected]>---
Changes in v3:
- Rebase on masterf9587d4045c6and 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.comChanges in v2:
- Rebase on current master and Zephyr Li's RV32 fix v2.
- Register the system TCG tests with Meson and use -bios loading.
- Mergesmcdeleg-counter-rv32.Sandsmcdeleg-event-rv32.Sinto
smcdeleg-rv32.S(patch 6).
- Use CSR names or local CSR-number macros in tests.
- UseUINT32_MAXfor 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 resetsystem/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:fc09febb37e6eff15ef860aa49a5d4f85a0f2ee9Best regards,
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]>Signed-off-by TANG Tiancheng <[email protected]>---
Changes in v3:
- Rebase on masterf9587d4045c6and 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.comChanges in v2:
- Rebase on current master and Zephyr Li's RV32 fix v2.
- Register the system TCG tests with Meson and use -bios loading.
- Mergesmcdeleg-counter-rv32.Sandsmcdeleg-event-rv32.Sintosmcdeleg-rv32.S(patch 6).
- Use CSR names or local CSR-number macros in tests.
- UseUINT32_MAXfor 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 resetsystem/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:fc09febb37e6eff15ef860aa49a5d4f85a0f2ee9Best regards,