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]>
---
 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