The PMU is already counting cycles by calculating time elapsed in
nanoseconds. Counting instructions is a different matter and requires
another approach.
This patch adds the capability of counting completed instructions (Perf
event PM_INST_CMPL) by counting the amount of instructions translated in
each translation block right before exiting it.
A new pmu_count_insns() helper in translation.c was added to do that.
After verifying that the PMU is counting instructions, call
helper_insns_inc(). This new helper from power8-pmu.c will add the
instructions to the relevant counters. It'll also be responsible for
triggering counter negative overflows as it is already being done with
cycles.
To verify whether the PMU is counting instructions or now, a new hflags
named 'HFLAGS_INSN_CNT' is introduced. This flag will match the internal
state of the PMU. We're be using this flag to avoid calling
helper_insn_inc() when we do not have a valid instruction event being
sampled.
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
Signed-off-by: Daniel Henrique Barboza <danielhb413@gmail.com>
Message-Id: <
20211201151734.654994-7-danielhb413@gmail.com>
Signed-off-by: Cédric Le Goater <clg@kaod.org>
HFLAGS_PR = 14, /* MSR_PR */
HFLAGS_PMCC0 = 15, /* MMCR0 PMCC bit 0 */
HFLAGS_PMCC1 = 16, /* MMCR0 PMCC bit 1 */
+ HFLAGS_INSN_CNT = 17, /* PMU instruction count enabled */
HFLAGS_VSX = 23, /* MSR_VSX if cpu has VSX */
HFLAGS_VR = 25, /* MSR_VR if cpu has VRE */
DEF_HELPER_2(store_mmcr1, void, env, tl)
DEF_HELPER_3(store_pmc, void, env, i32, i64)
DEF_HELPER_2(read_pmc, tl, env, i32)
+DEF_HELPER_2(insns_inc, void, env, i32)
#endif
DEF_HELPER_1(check_tlb_flush_local, void, env)
DEF_HELPER_1(check_tlb_flush_global, void, env)
#include "exec/exec-all.h"
#include "sysemu/kvm.h"
#include "helper_regs.h"
+#include "power8-pmu.h"
/* Swap temporary saved registers with GPRs */
void hreg_swap_gpr_tgpr(CPUPPCState *env)
hflags |= 1 << HFLAGS_HV;
}
+#if defined(TARGET_PPC64)
+ if (pmu_insn_cnt_enabled(env)) {
+ hflags |= 1 << HFLAGS_INSN_CNT;
+ }
+#endif
+
/*
* This is our encoding for server processors. The architecture
* specifies that there is no such thing as userspace with
*/
gen_icount_io_start(ctx);
gen_helper_store_mmcr0(cpu_env, val);
+
+ /*
+ * End the translation block because MMCR0 writes can change
+ * ctx->pmu_insn_cnt.
+ */
+ ctx->base.is_jmp = DISAS_EXIT_UPDATE;
}
void spr_write_MMCR0_ureg(DisasContext *ctx, int sprn, int gprn)
return evt_type;
}
+bool pmu_insn_cnt_enabled(CPUPPCState *env)
+{
+ int sprn;
+
+ for (sprn = SPR_POWER_PMC1; sprn <= SPR_POWER_PMC5; sprn++) {
+ if (pmc_get_event(env, sprn) == PMU_EVENT_INSTRUCTIONS) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static bool pmu_increment_insns(CPUPPCState *env, uint32_t num_insns)
+{
+ bool overflow_triggered = false;
+ int sprn;
+
+ /* PMC6 never counts instructions */
+ for (sprn = SPR_POWER_PMC1; sprn <= SPR_POWER_PMC5; sprn++) {
+ if (pmc_get_event(env, sprn) != PMU_EVENT_INSTRUCTIONS) {
+ continue;
+ }
+
+ env->spr[sprn] += num_insns;
+
+ if (env->spr[sprn] >= PMC_COUNTER_NEGATIVE_VAL &&
+ pmc_has_overflow_enabled(env, sprn)) {
+
+ overflow_triggered = true;
+
+ /*
+ * The real PMU will always trigger a counter overflow with
+ * PMC_COUNTER_NEGATIVE_VAL. We don't have an easy way to
+ * do that since we're counting block of instructions at
+ * the end of each translation block, and we're probably
+ * passing this value at this point.
+ *
+ * Let's write PMC_COUNTER_NEGATIVE_VAL to the overflowed
+ * counter to simulate what the real hardware would do.
+ */
+ env->spr[sprn] = PMC_COUNTER_NEGATIVE_VAL;
+ }
+ }
+
+ return overflow_triggered;
+}
+
static void pmu_update_cycles(CPUPPCState *env)
{
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
env->spr[SPR_POWER_MMCR0] = value;
- /* MMCR0 writes can change HFLAGS_PMCCCLEAR */
+ /* MMCR0 writes can change HFLAGS_PMCCCLEAR and HFLAGS_INSN_CNT */
hreg_compute_hflags(env);
/* Update cycle overflow timers with the current MMCR0 state */
pmu_update_cycles(env);
env->spr[SPR_POWER_MMCR1] = value;
+
+ /* MMCR1 writes can change HFLAGS_INSN_CNT */
+ hreg_compute_hflags(env);
}
target_ulong helper_read_pmc(CPUPPCState *env, uint32_t sprn)
return;
}
+/* This helper assumes that the PMC is running. */
+void helper_insns_inc(CPUPPCState *env, uint32_t num_insns)
+{
+ bool overflow_triggered;
+ PowerPCCPU *cpu;
+
+ overflow_triggered = pmu_increment_insns(env, num_insns);
+
+ if (overflow_triggered) {
+ cpu = env_archcpu(env);
+ fire_PMC_interrupt(cpu);
+ }
+}
+
static void cpu_ppc_pmu_timer_cb(void *opaque)
{
PowerPCCPU *cpu = opaque;
#include "qemu/main-loop.h"
void cpu_ppc_pmu_init(CPUPPCState *env);
+bool pmu_insn_cnt_enabled(CPUPPCState *env);
#endif
bool hr;
bool mmcr0_pmcc0;
bool mmcr0_pmcc1;
+ bool pmu_insn_cnt;
ppc_spr_t *spr_cb; /* Needed to check rights for mfspr/mtspr */
int singlestep_enabled;
uint32_t flags;
#endif
}
+#if defined(TARGET_PPC64)
+static void pmu_count_insns(DisasContext *ctx)
+{
+ /*
+ * Do not bother calling the helper if the PMU isn't counting
+ * instructions.
+ */
+ if (!ctx->pmu_insn_cnt) {
+ return;
+ }
+
+ #if !defined(CONFIG_USER_ONLY)
+ /*
+ * The PMU insns_inc() helper stops the internal PMU timer if a
+ * counter overflows happens. In that case, if the guest is
+ * running with icount and we do not handle it beforehand,
+ * the helper can trigger a 'bad icount read'.
+ */
+ gen_icount_io_start(ctx);
+
+ gen_helper_insns_inc(cpu_env, tcg_constant_i32(ctx->base.num_insns));
+#else
+ /*
+ * User mode can read (but not write) PMC5 and start/stop
+ * the PMU via MMCR0_FC. In this case just increment
+ * PMC5 with base.num_insns.
+ */
+ TCGv t0 = tcg_temp_new();
+
+ gen_load_spr(t0, SPR_POWER_PMC5);
+ tcg_gen_addi_tl(t0, t0, ctx->base.num_insns);
+ gen_store_spr(SPR_POWER_PMC5, t0);
+
+ tcg_temp_free(t0);
+#endif /* #if !defined(CONFIG_USER_ONLY) */
+}
+#else
+static void pmu_count_insns(DisasContext *ctx)
+{
+ return;
+}
+#endif /* #if defined(TARGET_PPC64) */
+
static inline bool use_goto_tb(DisasContext *ctx, target_ulong dest)
{
return translator_use_goto_tb(&ctx->base, dest);
if (unlikely(ctx->singlestep_enabled)) {
gen_debug_exception(ctx);
} else {
+ /*
+ * tcg_gen_lookup_and_goto_ptr will exit the TB if
+ * CF_NO_GOTO_PTR is set. Count insns now.
+ */
+ if (ctx->base.tb->flags & CF_NO_GOTO_PTR) {
+ pmu_count_insns(ctx);
+ }
+
tcg_gen_lookup_and_goto_ptr();
}
}
dest = (uint32_t) dest;
}
if (use_goto_tb(ctx, dest)) {
+ pmu_count_insns(ctx);
tcg_gen_goto_tb(n);
tcg_gen_movi_tl(cpu_nip, dest & ~3);
tcg_gen_exit_tb(ctx->base.tb, n);
ctx->hr = (hflags >> HFLAGS_HR) & 1;
ctx->mmcr0_pmcc0 = (hflags >> HFLAGS_PMCC0) & 1;
ctx->mmcr0_pmcc1 = (hflags >> HFLAGS_PMCC1) & 1;
+ ctx->pmu_insn_cnt = (hflags >> HFLAGS_INSN_CNT) & 1;
ctx->singlestep_enabled = 0;
if ((hflags >> HFLAGS_SE) & 1) {
switch (is_jmp) {
case DISAS_TOO_MANY:
if (use_goto_tb(ctx, nip)) {
+ pmu_count_insns(ctx);
tcg_gen_goto_tb(0);
gen_update_nip(ctx, nip);
tcg_gen_exit_tb(ctx->base.tb, 0);
gen_update_nip(ctx, nip);
/* fall through */
case DISAS_CHAIN:
+ /*
+ * tcg_gen_lookup_and_goto_ptr will exit the TB if
+ * CF_NO_GOTO_PTR is set. Count insns now.
+ */
+ if (ctx->base.tb->flags & CF_NO_GOTO_PTR) {
+ pmu_count_insns(ctx);
+ }
+
tcg_gen_lookup_and_goto_ptr();
break;
gen_update_nip(ctx, nip);
/* fall through */
case DISAS_EXIT:
+ pmu_count_insns(ctx);
tcg_gen_exit_tb(NULL, 0);
break;