From: kettenis Date: Mon, 9 Jan 2023 15:22:53 +0000 (+0000) Subject: Allwinner hardware sucks! The ARM generic timer on the A64 has a bug X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=aeac2e0a35f5acaa7ab2f64c201ad7f62eaf0a38;p=openbsd Allwinner hardware sucks! The ARM generic timer on the A64 has a bug where the bottom 9 bits of the counter register can't be trusted if any of the higher bits are rolling over. This is an unpublished errata so the details aren't known. Adopt the same workaround that Linux has. This will disable the userland timecounter support on hardware affected by the hardware. We will need a similar workaround in libc to restore that functionality. tested by semarie@ ok cheloha@ --- diff --git a/sys/arch/arm64/dev/agtimer.c b/sys/arch/arm64/dev/agtimer.c index 658bfbf92e7..f5da40babef 100644 --- a/sys/arch/arm64/dev/agtimer.c +++ b/sys/arch/arm64/dev/agtimer.c @@ -1,4 +1,4 @@ -/* $OpenBSD: agtimer.c,v 1.20 2022/11/08 17:56:38 cheloha Exp $ */ +/* $OpenBSD: agtimer.c,v 1.21 2023/01/09 15:22:53 kettenis Exp $ */ /* * Copyright (c) 2011 Dale Rahn * Copyright (c) 2013 Patrick Wildt @@ -42,10 +42,11 @@ #define TIMER_FREQUENCY 24 * 1000 * 1000 /* ARM core clock */ int32_t agtimer_frequency = TIMER_FREQUENCY; -u_int agtimer_get_timecount(struct timecounter *); +u_int agtimer_get_timecount_default(struct timecounter *); +u_int agtimer_get_timecount_sun50i(struct timecounter *); static struct timecounter agtimer_timecounter = { - .tc_get_timecount = agtimer_get_timecount, + .tc_get_timecount = agtimer_get_timecount_default, .tc_poll_pps = NULL, .tc_counter_mask = 0xffffffff, .tc_frequency = 0, @@ -67,7 +68,6 @@ struct agtimer_softc { int agtimer_match(struct device *, void *, void *); void agtimer_attach(struct device *, struct device *, void *); -uint64_t agtimer_readcnt64(void); int agtimer_intr(void *); void agtimer_cpu_initclocks(void); void agtimer_delay(u_int); @@ -91,7 +91,7 @@ struct intrclock agtimer_intrclock = { }; uint64_t -agtimer_readcnt64(void) +agtimer_readcnt64_default(void) { uint64_t val0, val1; @@ -107,6 +107,26 @@ agtimer_readcnt64(void) return ((val0 ^ val1) & 0x100000000ULL) ? val0 : val1; } +uint64_t +agtimer_readcnt64_sun50i(void) +{ + uint64_t val; + int retry; + + __asm volatile("isb" ::: "memory"); + for (retry = 0; retry < 150; retry++) { + __asm volatile("mrs %x0, CNTVCT_EL0" : "=r" (val)); + + if (((val + 1) & 0x1ff) > 1) + break; + } + KASSERT(retry < 150); + + return val; +} + +uint64_t (*agtimer_readcnt64)(void) = agtimer_readcnt64_default; + static inline uint64_t agtimer_get_freq(void) { @@ -173,6 +193,18 @@ agtimer_attach(struct device *parent, struct device *self, void *aux) printf(": %u kHz\n", sc->sc_ticks_per_second / 1000); + /* + * The Allwinner A64 has an erratum where the bottom 9 bits of + * the counter register can't be trusted if any of the higher + * bits are rolling over. + */ + if (OF_getpropbool(sc->sc_node, "allwinner,erratum-unknown1")) { + agtimer_readcnt64 = agtimer_readcnt64_sun50i; + agtimer_timecounter.tc_get_timecount = + agtimer_get_timecount_sun50i; + agtimer_timecounter.tc_user = TC_AGTIMER_SUN50I; + } + /* * private timer and interrupts not enabled until * timer configures @@ -189,7 +221,7 @@ agtimer_attach(struct device *parent, struct device *self, void *aux) } u_int -agtimer_get_timecount(struct timecounter *tc) +agtimer_get_timecount_default(struct timecounter *tc) { uint64_t val; @@ -202,6 +234,12 @@ agtimer_get_timecount(struct timecounter *tc) return (val & 0xffffffff); } +u_int +agtimer_get_timecount_sun50i(struct timecounter *tc) +{ + return agtimer_readcnt64_sun50i(); +} + void agtimer_rearm(void *cookie, uint64_t nsecs) { diff --git a/sys/arch/arm64/include/timetc.h b/sys/arch/arm64/include/timetc.h index 653a7d33189..55845d6d02b 100644 --- a/sys/arch/arm64/include/timetc.h +++ b/sys/arch/arm64/include/timetc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: timetc.h,v 1.2 2020/07/15 22:58:33 kettenis Exp $ */ +/* $OpenBSD: timetc.h,v 1.3 2023/01/09 15:22:53 kettenis Exp $ */ /* * Copyright (c) 2020 Paul Irofti * @@ -18,6 +18,7 @@ #ifndef _MACHINE_TIMETC_H_ #define _MACHINE_TIMETC_H_ -#define TC_AGTIMER 1 +#define TC_AGTIMER 1 +#define TC_AGTIMER_SUN50I 2 #endif /* _MACHINE_TIMETC_H_ */