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

Thanks,
Chao

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

Reply via email to