Commit 4e7beb0c authored by Aaron Lindsay OS's avatar Aaron Lindsay OS Committed by Peter Maydell
Browse files

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: default avatarAaron Lindsay <aaron@os.amperecomputing.com>
Reviewed-by: default avatarRichard Henderson <richard.henderson@linaro.org>
Message-id: 20190124162401.5111-3-aaron@os.amperecomputing.com
Signed-off-by: default avatarPeter Maydell <peter.maydell@linaro.org>
parent f4efb4b2
Loading
Loading
Loading
Loading
+12 −0
Original line number Diff line number Diff line
@@ -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;
+10 −0
Original line number Diff line number Diff line
@@ -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
 */
+70 −2
Original line number Diff line number Diff line
@@ -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)
{