target/arm: Add a timer to predict PMU counter overflow
Make PMU overflow interrupts more accurate by using a timer to predict
when they will overflow rather than waiting for an event to occur which
allows us to otherwise check them.
Signed-off-by: Aaron Lindsay <aaron@os.amperecomputing.com>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20190124162401.5111-3-aaron@os.amperecomputing.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/target/arm/helper.c b/target/arm/helper.c
index c27e349..e6f6918 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -1021,6 +1021,13 @@
* counters hold a difference from the return value from this function
*/
uint64_t (*get_count)(CPUARMState *);
+ /*
+ * Return how many nanoseconds it will take (at a minimum) for count events
+ * to occur. A negative value indicates the counter will never overflow, or
+ * that the counter has otherwise arranged for the overflow bit to be set
+ * and the PMU interrupt to be raised on overflow.
+ */
+ int64_t (*ns_per_count)(uint64_t);
} pm_event;
static bool event_always_supported(CPUARMState *env)
@@ -1037,6 +1044,11 @@
return 0;
}
+static int64_t swinc_ns_per(uint64_t ignored)
+{
+ return -1;
+}
+
/*
* Return the underlying cycle count for the PMU cycle counters. If we're in
* usermode, simply return 0.
@@ -1052,6 +1064,11 @@
}
#ifndef CONFIG_USER_ONLY
+static int64_t cycles_ns_per(uint64_t cycles)
+{
+ return (ARM_CPU_FREQ / NANOSECONDS_PER_SECOND) * cycles;
+}
+
static bool instructions_supported(CPUARMState *env)
{
return use_icount == 1 /* Precise instruction counting */;
@@ -1061,21 +1078,29 @@
{
return (uint64_t)cpu_get_icount_raw();
}
+
+static int64_t instructions_ns_per(uint64_t icount)
+{
+ return cpu_icount_to_ns((int64_t)icount);
+}
#endif
static const pm_event pm_events[] = {
{ .number = 0x000, /* SW_INCR */
.supported = event_always_supported,
.get_count = swinc_get_count,
+ .ns_per_count = swinc_ns_per,
},
#ifndef CONFIG_USER_ONLY
{ .number = 0x008, /* INST_RETIRED, Instruction architecturally executed */
.supported = instructions_supported,
.get_count = instructions_get_count,
+ .ns_per_count = instructions_ns_per,
},
{ .number = 0x011, /* CPU_CYCLES, Cycle */
.supported = event_always_supported,
.get_count = cycles_get_count,
+ .ns_per_count = cycles_ns_per,
}
#endif
};
@@ -1340,13 +1365,27 @@
void pmccntr_op_finish(CPUARMState *env)
{
if (pmu_counter_enabled(env, 31)) {
- uint64_t prev_cycles = env->cp15.c15_ccnt_delta;
+#ifndef CONFIG_USER_ONLY
+ /* Calculate when the counter will next overflow */
+ uint64_t remaining_cycles = -env->cp15.c15_ccnt;
+ if (!(env->cp15.c9_pmcr & PMCRLC)) {
+ remaining_cycles = (uint32_t)remaining_cycles;
+ }
+ int64_t overflow_in = cycles_ns_per(remaining_cycles);
+ if (overflow_in > 0) {
+ int64_t overflow_at = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ overflow_in;
+ ARMCPU *cpu = arm_env_get_cpu(env);
+ timer_mod_anticipate_ns(cpu->pmu_timer, overflow_at);
+ }
+#endif
+
+ uint64_t prev_cycles = env->cp15.c15_ccnt_delta;
if (env->cp15.c9_pmcr & PMCRD) {
/* Increment once every 64 processor clock cycles */
prev_cycles /= 64;
}
-
env->cp15.c15_ccnt_delta = prev_cycles - env->cp15.c15_ccnt;
}
}
@@ -1376,6 +1415,21 @@
static void pmevcntr_op_finish(CPUARMState *env, uint8_t counter)
{
if (pmu_counter_enabled(env, counter)) {
+#ifndef CONFIG_USER_ONLY
+ uint16_t event = env->cp15.c14_pmevtyper[counter] & PMXEVTYPER_EVTCOUNT;
+ uint16_t event_idx = supported_event_map[event];
+ uint64_t delta = UINT32_MAX -
+ (uint32_t)env->cp15.c14_pmevcntr[counter] + 1;
+ int64_t overflow_in = pm_events[event_idx].ns_per_count(delta);
+
+ if (overflow_in > 0) {
+ int64_t overflow_at = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ overflow_in;
+ ARMCPU *cpu = arm_env_get_cpu(env);
+ timer_mod_anticipate_ns(cpu->pmu_timer, overflow_at);
+ }
+#endif
+
env->cp15.c14_pmevcntr_delta[counter] -=
env->cp15.c14_pmevcntr[counter];
}
@@ -1409,6 +1463,20 @@
pmu_op_finish(&cpu->env);
}
+void arm_pmu_timer_cb(void *opaque)
+{
+ ARMCPU *cpu = opaque;
+
+ /*
+ * Update all the counter values based on the current underlying counts,
+ * triggering interrupts to be raised, if necessary. pmu_op_finish() also
+ * has the effect of setting the cpu->pmu_timer to the next earliest time a
+ * counter may expire.
+ */
+ pmu_op_start(&cpu->env);
+ pmu_op_finish(&cpu->env);
+}
+
static void pmcr_write(CPUARMState *env, const ARMCPRegInfo *ri,
uint64_t value)
{