linuxtorvalds / rpms / kernel

Forked from rpms/kernel 2 years ago
Clone
e336be
From patchwork Fri May 11 02:27:50 2018
e336be
Content-Type: text/plain; charset="utf-8"
e336be
MIME-Version: 1.0
e336be
Content-Transfer-Encoding: 8bit
e336be
Subject: [1/2] arm64: arch_timer: Workaround for Allwinner A64 timer
e336be
 instability
e336be
From: Samuel Holland <samuel@sholland.org>
e336be
X-Patchwork-Id: 10392891
e336be
Message-Id: <20180511022751.9096-2-samuel@sholland.org>
e336be
To: Maxime Ripard <maxime.ripard@bootlin.com>, Chen-Yu Tsai <wens@csie.org>, 
e336be
 Catalin Marinas <catalin.marinas@arm.com>,
e336be
 Will Deacon <will.deacon@arm.com>,
e336be
 Daniel Lezcano <daniel.lezcano@linaro.org>,
e336be
 Thomas Gleixner <tglx@linutronix.de>, Marc Zyngier <marc.zyngier@arm.com>
e336be
Cc: linux-sunxi@googlegroups.com, linux-kernel@vger.kernel.org,
e336be
 linux-arm-kernel@lists.infradead.org, Samuel Holland <samuel@sholland.org>
e336be
Date: Thu, 10 May 2018 21:27:50 -0500
e336be
e336be
The Allwinner A64 SoC is known [1] to have an unstable architectural
e336be
timer, which manifests itself most obviously in the time jumping forward
e336be
a multiple of 95 years [2][3]. This coincides with 2^56 cycles at a
e336be
timer frequency of 24 MHz, implying that the time went slightly backward
e336be
(and this was interpreted by the kernel as it jumping forward and
e336be
wrapping around past the epoch).
e336be
e336be
Further investigation revealed instability in the low bits of CNTVCT at
e336be
the point a high bit rolls over. This leads to power-of-two cycle
e336be
forward and backward jumps. (Testing shows that forward jumps are about
e336be
twice as likely as backward jumps.)
e336be
e336be
Without trapping reads to CNTVCT, a userspace program is able to read it
e336be
in a loop faster than it changes. A test program running on all 4 CPU
e336be
cores that reported jumps larger than 100 ms was run for 13.6 hours and
e336be
reported the following:
e336be
e336be
 Count | Event
e336be
-------+---------------------------
e336be
  9940 | jumped backward      699ms
e336be
   268 | jumped backward     1398ms
e336be
     1 | jumped backward     2097ms
e336be
 16020 | jumped forward       175ms
e336be
  6443 | jumped forward       699ms
e336be
  2976 | jumped forward      1398ms
e336be
     9 | jumped forward    356516ms
e336be
     9 | jumped forward    357215ms
e336be
     4 | jumped forward    714430ms
e336be
     1 | jumped forward   3578440ms
e336be
e336be
This works out to a jump larger than 100 ms about every 5.5 seconds on
e336be
each CPU core.
e336be
e336be
The largest jump (almost an hour!) was the following sequence of reads:
e336be
      0x0000007fffffffff → 0x00000093feffffff → 0x0000008000000000
e336be
e336be
Note that the middle bits don't necessarily all read as all zeroes or
e336be
all ones during the anomalous behavior; however the low 11 bits checked
e336be
by the function in this patch have never been observed with any other
e336be
value.
e336be
e336be
Also note that smaller jumps are much more common, with the smallest
e336be
backward jumps of 2048 cycles observed over 400 times per second on each
e336be
core. (Of course, this is partially due to lower bits rolling over more
e336be
frequently.) Any one of these could have caused the 95 year time skip.
e336be
e336be
Similar anomalies were observed while reading CNTPCT (after patching the
e336be
kernel to allow reads from userspace). However, the jumps are much less
e336be
frequent, and only small jumps were observed. The same program as before
e336be
(except now reading CNTPCT) observed after 72 hours:
e336be
e336be
 Count | Event
e336be
-------+---------------------------
e336be
    17 | jumped backward      699ms
e336be
    52 | jumped forward       175ms
e336be
  2831 | jumped forward       699ms
e336be
     5 | jumped forward      1398ms
e336be
Acked-by: Maxime Ripard <maxime.ripard@bootlin.com>
e336be
Tested-by: Andre Przywara <andre.przywara@arm.com>
e336be
e336be
========================================================================
e336be
e336be
Because the CPU can read the CNTPCT/CNTVCT registers faster than they
e336be
change, performing two reads of the register and comparing the high bits
e336be
(like other workarounds) is not a workable solution. And because the
e336be
timer can jump both forward and backward, no pair of reads can
e336be
distinguish a good value from a bad one. The only way to guarantee a
e336be
good value from consecutive reads would be to read _three_ times, and
e336be
take the middle value iff the three values are 1) individually unique
e336be
and 2) increasing. This takes at minimum 3 cycles (125 ns), or more if
e336be
an anomaly is detected.
e336be
e336be
However, since there is a distinct pattern to the bad values, we can
e336be
optimize the common case (2046/2048 of the time) to a single read by
e336be
simply ignoring values that match the pattern. This still takes no more
e336be
than 3 cycles in the worst case, and requires much less code.
e336be
e336be
[1]: https://github.com/armbian/build/commit/a08cd6fe7ae9
e336be
[2]: https://forum.armbian.com/topic/3458-a64-datetime-clock-issue/
e336be
[3]: https://irclog.whitequark.org/linux-sunxi/2018-01-26
e336be
e336be
Signed-off-by: Samuel Holland <samuel@sholland.org>
e336be
---
e336be
 drivers/clocksource/Kconfig          | 11 ++++++++++
e336be
 drivers/clocksource/arm_arch_timer.c | 39 ++++++++++++++++++++++++++++++++++++
e336be
 2 files changed, 50 insertions(+)
e336be
e336be
diff --git a/drivers/clocksource/Kconfig b/drivers/clocksource/Kconfig
e336be
index 8e8a09755d10..7a5d434dd30b 100644
e336be
--- a/drivers/clocksource/Kconfig
e336be
+++ b/drivers/clocksource/Kconfig
e336be
@@ -364,6 +364,17 @@ config ARM64_ERRATUM_858921
e336be
 	  The workaround will be dynamically enabled when an affected
e336be
 	  core is detected.
e336be
 
e336be
+config SUN50I_A64_UNSTABLE_TIMER
e336be
+	bool "Workaround for Allwinner A64 timer instability"
e336be
+	default y
e336be
+	depends on ARM_ARCH_TIMER && ARM64 && ARCH_SUNXI
e336be
+	select ARM_ARCH_TIMER_OOL_WORKAROUND
e336be
+	help
e336be
+	  This option enables a workaround for instability in the timer on
e336be
+	  the Allwinner A64 SoC. The workaround will only be active if the
e336be
+	  allwinner,sun50i-a64-unstable-timer property is found in the
e336be
+	  timer node.
e336be
+
e336be
 config ARM_GLOBAL_TIMER
e336be
 	bool "Support for the ARM global timer" if COMPILE_TEST
e336be
 	select TIMER_OF if OF
e336be
diff --git a/drivers/clocksource/arm_arch_timer.c b/drivers/clocksource/arm_arch_timer.c
e336be
index 57cb2f00fc07..66ce13578c52 100644
e336be
--- a/drivers/clocksource/arm_arch_timer.c
e336be
+++ b/drivers/clocksource/arm_arch_timer.c
e336be
@@ -319,6 +319,36 @@ static u64 notrace arm64_858921_read_cntvct_el0(void)
e336be
 }
e336be
 #endif
e336be
 
e336be
+#ifdef CONFIG_SUN50I_A64_UNSTABLE_TIMER
e336be
+/*
e336be
+ * The low bits of each register can transiently read as all ones or all zeroes
e336be
+ * when bit 11 or greater rolls over. Since the value can jump both backward
e336be
+ * (7ff -> 000 -> 800) and forward (7ff -> fff -> 800), it is simplest to just
e336be
+ * ignore register values with all ones or zeros in the low bits.
e336be
+ */
e336be
+static u64 notrace sun50i_a64_read_cntpct_el0(void)
e336be
+{
e336be
+	u64 val;
e336be
+
e336be
+	do {
e336be
+		val = read_sysreg(cntpct_el0);
e336be
+	} while (((val + 1) & GENMASK(10, 0)) <= 1);
e336be
+
e336be
+	return val;
e336be
+}
e336be
+
e336be
+static u64 notrace sun50i_a64_read_cntvct_el0(void)
e336be
+{
e336be
+	u64 val;
e336be
+
e336be
+	do {
e336be
+		val = read_sysreg(cntvct_el0);
e336be
+	} while (((val + 1) & GENMASK(10, 0)) <= 1);
e336be
+
e336be
+	return val;
e336be
+}
e336be
+#endif
e336be
+
e336be
 #ifdef CONFIG_ARM_ARCH_TIMER_OOL_WORKAROUND
e336be
 DEFINE_PER_CPU(const struct arch_timer_erratum_workaround *, timer_unstable_counter_workaround);
e336be
 EXPORT_SYMBOL_GPL(timer_unstable_counter_workaround);
e336be
@@ -408,6 +438,15 @@ static const struct arch_timer_erratum_workaround ool_workarounds[] = {
e336be
 		.read_cntvct_el0 = arm64_858921_read_cntvct_el0,
e336be
 	},
e336be
 #endif
e336be
+#ifdef CONFIG_SUN50I_A64_UNSTABLE_TIMER
e336be
+	{
e336be
+		.match_type = ate_match_dt,
e336be
+		.id = "allwinner,sun50i-a64-unstable-timer",
e336be
+		.desc = "Allwinner A64 timer instability",
e336be
+		.read_cntpct_el0 = sun50i_a64_read_cntpct_el0,
e336be
+		.read_cntvct_el0 = sun50i_a64_read_cntvct_el0,
e336be
+	},
e336be
+#endif
e336be
 };
e336be
 
e336be
 typedef bool (*ate_match_fn_t)(const struct arch_timer_erratum_workaround *,