summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--target/arm/cpu.c61
-rw-r--r--target/arm/helper.c9
2 files changed, 59 insertions, 11 deletions
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 0abe288e38..d62fd5fdc6 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -974,10 +974,12 @@ static void arm_cpu_initfn(Object *obj)
if (tcg_enabled()) {
cpu->psci_version = 2; /* TCG implements PSCI 0.2 */
}
-
- cpu->gt_cntfrq_hz = NANOSECONDS_PER_SECOND / GTIMER_SCALE;
}
+static Property arm_cpu_gt_cntfrq_property =
+ DEFINE_PROP_UINT64("cntfrq", ARMCPU, gt_cntfrq_hz,
+ NANOSECONDS_PER_SECOND / GTIMER_SCALE);
+
static Property arm_cpu_reset_cbar_property =
DEFINE_PROP_UINT64("reset-cbar", ARMCPU, reset_cbar, 0);
@@ -1059,6 +1061,24 @@ static void arm_set_init_svtor(Object *obj, Visitor *v, const char *name,
unsigned int gt_cntfrq_period_ns(ARMCPU *cpu)
{
+ /*
+ * The exact approach to calculating guest ticks is:
+ *
+ * muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), cpu->gt_cntfrq_hz,
+ * NANOSECONDS_PER_SECOND);
+ *
+ * We don't do that. Rather we intentionally use integer division
+ * truncation below and in the caller for the conversion of host monotonic
+ * time to guest ticks to provide the exact inverse for the semantics of
+ * the QEMUTimer scale factor. QEMUTimer's scale facter is an integer, so
+ * it loses precision when representing frequencies where
+ * `(NANOSECONDS_PER_SECOND % cpu->gt_cntfrq) > 0` holds. Failing to
+ * provide an exact inverse leads to scheduling timers with negative
+ * periods, which in turn leads to sticky behaviour in the guest.
+ *
+ * Finally, CNTFRQ is effectively capped at 1GHz to ensure our scale factor
+ * cannot become zero.
+ */
return NANOSECONDS_PER_SECOND > cpu->gt_cntfrq_hz ?
NANOSECONDS_PER_SECOND / cpu->gt_cntfrq_hz : 1;
}
@@ -1180,6 +1200,11 @@ void arm_cpu_post_init(Object *obj)
qdev_property_add_static(DEVICE(obj), &arm_cpu_cfgend_property,
&error_abort);
+
+ if (arm_feature(&cpu->env, ARM_FEATURE_GENERIC_TIMER)) {
+ qdev_property_add_static(DEVICE(cpu), &arm_cpu_gt_cntfrq_property,
+ &error_abort);
+ }
}
static void arm_cpu_finalizefn(Object *obj)
@@ -1259,14 +1284,30 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
}
}
- cpu->gt_timer[GTIMER_PHYS] = timer_new(QEMU_CLOCK_VIRTUAL, GTIMER_SCALE,
- arm_gt_ptimer_cb, cpu);
- cpu->gt_timer[GTIMER_VIRT] = timer_new(QEMU_CLOCK_VIRTUAL, GTIMER_SCALE,
- arm_gt_vtimer_cb, cpu);
- cpu->gt_timer[GTIMER_HYP] = timer_new(QEMU_CLOCK_VIRTUAL, GTIMER_SCALE,
- arm_gt_htimer_cb, cpu);
- cpu->gt_timer[GTIMER_SEC] = timer_new(QEMU_CLOCK_VIRTUAL, GTIMER_SCALE,
- arm_gt_stimer_cb, cpu);
+
+ {
+ uint64_t scale;
+
+ if (arm_feature(env, ARM_FEATURE_GENERIC_TIMER)) {
+ if (!cpu->gt_cntfrq_hz) {
+ error_setg(errp, "Invalid CNTFRQ: %"PRId64"Hz",
+ cpu->gt_cntfrq_hz);
+ return;
+ }
+ scale = gt_cntfrq_period_ns(cpu);
+ } else {
+ scale = GTIMER_SCALE;
+ }
+
+ cpu->gt_timer[GTIMER_PHYS] = timer_new(QEMU_CLOCK_VIRTUAL, scale,
+ arm_gt_ptimer_cb, cpu);
+ cpu->gt_timer[GTIMER_VIRT] = timer_new(QEMU_CLOCK_VIRTUAL, scale,
+ arm_gt_vtimer_cb, cpu);
+ cpu->gt_timer[GTIMER_HYP] = timer_new(QEMU_CLOCK_VIRTUAL, scale,
+ arm_gt_htimer_cb, cpu);
+ cpu->gt_timer[GTIMER_SEC] = timer_new(QEMU_CLOCK_VIRTUAL, scale,
+ arm_gt_stimer_cb, cpu);
+ }
#endif
cpu_exec_realizefn(cs, &local_err);
diff --git a/target/arm/helper.c b/target/arm/helper.c
index 85963789f7..1d9af2d8b2 100644
--- a/target/arm/helper.c
+++ b/target/arm/helper.c
@@ -2723,6 +2723,13 @@ void arm_gt_stimer_cb(void *opaque)
gt_recalc_timer(cpu, GTIMER_SEC);
}
+static void arm_gt_cntfrq_reset(CPUARMState *env, const ARMCPRegInfo *opaque)
+{
+ ARMCPU *cpu = env_archcpu(env);
+
+ cpu->env.cp15.c14_cntfrq = cpu->gt_cntfrq_hz;
+}
+
static const ARMCPRegInfo generic_timer_cp_reginfo[] = {
/* Note that CNTFRQ is purely reads-as-written for the benefit
* of software; writing it doesn't actually change the timer frequency.
@@ -2737,7 +2744,7 @@ static const ARMCPRegInfo generic_timer_cp_reginfo[] = {
.opc0 = 3, .opc1 = 3, .crn = 14, .crm = 0, .opc2 = 0,
.access = PL1_RW | PL0_R, .accessfn = gt_cntfrq_access,
.fieldoffset = offsetof(CPUARMState, cp15.c14_cntfrq),
- .resetvalue = (1000 * 1000 * 1000) / GTIMER_SCALE,
+ .resetfn = arm_gt_cntfrq_reset,
},
/* overall control: mostly access permissions */
{ .name = "CNTKCTL", .state = ARM_CP_STATE_BOTH,