On Sun, May 20, 2018 at 07:03:55PM +0200, Sebastien Marie wrote:
> On Sat, May 19, 2018 at 08:30:12AM +0200, Sebastien Marie wrote:
> > Hi,
> >
> > Working on lang/rust on arm64, I experiment rustc panic due to not
> > monotonic timestamp. I tried to reproduce the problem in plain C.
> >
> > My test program is just a loop of calling
> > clock_gettime(CLOCK_MONOTONIC), and it checks if the result is monotonic
> > (by comparing with previous value).
> >
>
> Artturi Alm told me that it is a know hardware problem.
>
> The proposed patch for Linux has good documentation about it.
> https://patchwork.kernel.org/patch/10392885/
>
> The Allwinner A64 SoC is known [1] to have an unstable architectural
> timer, which manifests itself most obviously in the time jumping forward
> a multiple of 95 years [2][3]. This coincides with 2^56 cycles at a
> timer frequency of 24 MHz, implying that the time went slightly backward
> (and this was interpreted by the kernel as it jumping forward and
> wrapping around past the epoch).
>
> Further investigation revealed instability in the low bits of CNTVCT at
> the point a high bit rolls over. This leads to power-of-two cycle
> forward and backward jumps. (Testing shows that forward jumps are about
> twice as likely as backward jumps.)
>
> [...]
>
> Because the CPU can read the CNTPCT/CNTVCT registers faster than they
> change, performing two reads of the register and comparing the high bits
> (like other workarounds) is not a workable solution. And because the
> timer can jump both forward and backward, no pair of reads can
> distinguish a good value from a bad one. The only way to guarantee a
> good value from consecutive reads would be to read _three_ times, and
> take the middle value iff the three values are 1) individually unique
> and 2) increasing. This takes at minimum 3 cycles (125 ns), or more if
> an anomaly is detected.
>
> However, since there is a distinct pattern to the bad values, we can
> optimize the common case (2046/2048 of the time) to a single read by
> simply ignoring values that match the pattern. This still takes no more
> than 3 cycles in the worst case, and requires much less code.
>
> [1]: https://github.com/armbian/build/commit/a08cd6fe7ae9
> [2]: https://forum.armbian.com/topic/3458-a64-datetime-clock-issue/
> [3]: https://irclog.whitequark.org/linux-sunxi/2018-01-26
>
First run of monotonic.c w/o diff below did abort in a bit over minute,
and with the diff + "allwinner,sun50i-a64-unstable-timer;" added to the
timer-node in DT, i haven't seen the abort yet.
Diff below is by no means the correct fix alone, just something in-case
you want to continue your lang/rust project, as there's obviously other
uses of CNTVCT_EL0 in agtimer too, but fixing those somewhat requires
adding a pointer, and i don't feel like touching agtimer as is any
further(too much style(9) etc.).
-Artturi
To add the prop to the DT:
# cd <sdcard_mount>/allwinner
# dtc -I dtb -O dts -H epapr -o sun50i-a64-pine64-plus.dts
sun50i-a64-pine64-plus.dtb
# mv sun50i-a64-pine64-plus.dtb sun50i-a64-pine64-plus.dtb-orig
# vi sun50i-a64-pine64-plus.dts
seek for timer { ... };
add "allwinner,sun50i-a64-unstable-timer;" within { ... };
:wq
# dtc -I dts -O dtb -H epapr -o sun50i-a64-pine64-plus.dtb
sun50i-a64-pine64-plus.dts
diff --git sys/arch/arm64/dev/agtimer.c sys/arch/arm64/dev/agtimer.c
index 0e6e6a3bc6e..729dd5dfd5e 100644
--- sys/arch/arm64/dev/agtimer.c
+++ sys/arch/arm64/dev/agtimer.c
@@ -41,6 +41,7 @@
int32_t agtimer_frequency = TIMER_FREQUENCY;
u_int agtimer_get_timecount(struct timecounter *);
+u_int agtimer_get_timecount_unstable(struct timecounter *);
static struct timecounter agtimer_timecounter = {
agtimer_get_timecount, NULL, 0x7fffffff, 0, "agtimer", 0, NULL
@@ -154,6 +155,7 @@ agtimer_attach(struct device *parent, struct device *self,
void *aux)
{
struct agtimer_softc *sc = (struct agtimer_softc *)self;
struct fdt_attach_args *faa = aux;
+ int node = faa->fa_node;
sc->sc_node = faa->fa_node;
@@ -180,6 +182,9 @@ agtimer_attach(struct device *parent, struct device *self,
void *aux)
arm_clock_register(agtimer_cpu_initclocks, agtimer_delay,
agtimer_setstatclockrate, agtimer_startclock);
+ if (OF_getproplen(node, "allwinner,sun50i-a64-unstable-timer") != -1)
+ agtimer_timecounter.tc_get_timecount =
+ agtimer_get_timecount_unstable;
agtimer_timecounter.tc_frequency = sc->sc_ticks_per_second;
agtimer_timecounter.tc_priv = sc;
@@ -192,6 +197,19 @@ agtimer_get_timecount(struct timecounter *tc)
return agtimer_readcnt64();
}
+u_int
+agtimer_get_timecount_unstable(struct timecounter *tc)
+{
+ uint64_t val;
+
+ for (val = agtimer_readcnt64(); ((val + 1) & 0x7ff) <= 1;) {
+ __asm volatile("isb\n" ::: "memory");
+ __asm volatile("MRS %x0, CNTVCT_EL0\n" : "=r"(val));
+ }
+
+ return val;
+}
+
int
agtimer_intr(void *frame)
{