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>
This commit is contained in:
Aaron Lindsay OS 2019-02-01 14:55:45 +00:00 committed by Peter Maydell
parent f4efb4b2a1
commit 4e7beb0cc0
3 changed files with 92 additions and 2 deletions

View file

@ -836,6 +836,13 @@ static void arm_cpu_finalizefn(Object *obj)
QLIST_REMOVE(hook, node);
g_free(hook);
}
#ifndef CONFIG_USER_ONLY
if (cpu->pmu_timer) {
timer_del(cpu->pmu_timer);
timer_deinit(cpu->pmu_timer);
timer_free(cpu->pmu_timer);
}
#endif
}
static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
@ -1045,6 +1052,11 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
arm_register_pre_el_change_hook(cpu, &pmu_pre_el_change, 0);
arm_register_el_change_hook(cpu, &pmu_post_el_change, 0);
}
#ifndef CONFIG_USER_ONLY
cpu->pmu_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, arm_pmu_timer_cb,
cpu);
#endif
} else {
cpu->id_aa64dfr0 &= ~0xf00;
cpu->pmceid0 = 0;

View file

@ -746,6 +746,11 @@ struct ARMCPU {
/* Timers used by the generic (architected) timer */
QEMUTimer *gt_timer[NUM_GTIMERS];
/*
* Timer used by the PMU. Its state is restored after migration by
* pmu_op_finish() - it does not need other handling during migration
*/
QEMUTimer *pmu_timer;
/* GPIO outputs for generic timer */
qemu_irq gt_timer_outputs[NUM_GTIMERS];
/* GPIO output for GICv3 maintenance interrupt signal */
@ -1005,6 +1010,11 @@ void pmccntr_op_finish(CPUARMState *env);
void pmu_op_start(CPUARMState *env);
void pmu_op_finish(CPUARMState *env);
/*
* Called when a PMU counter is due to overflow
*/
void arm_pmu_timer_cb(void *opaque);
/**
* Functions to register as EL change hooks for PMU mode filtering
*/

View file

@ -1021,6 +1021,13 @@ typedef struct pm_event {
* 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 @@ static uint64_t swinc_get_count(CPUARMState *env)
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 @@ static uint64_t cycles_get_count(CPUARMState *env)
}
#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 @@ static uint64_t instructions_get_count(CPUARMState *env)
{
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_start(CPUARMState *env)
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_start(CPUARMState *env, uint8_t counter)
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 @@ void pmu_post_el_change(ARMCPU *cpu, void *ignored)
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)
{