Consolidate read code for timecounters

and fix possible overflow in bintime()/binuptime().

The algorithm to read the consistent snapshot of current timehand is
repeated in each accessor, including the details proper rollup
detection and synchronization with the writer.  In fact there are only
two different kind of readers: one for bintime()/binuptime() which has
to do the in-place calculation, and another kind which fetches some
member from struct timehand.

Extract the logic into type-checked macros, GETTHBINTIME() for bintime
calculation, and GETTHMEMBER() for safe read of a structure' member.
This way, the synchronization is only written in bintime_off() and
getthmember().

In bintime_off(), use overflow-safe calculation of th_scale *
delta(timecounter).  In tc_windup, pre-calculate the min delta value
which overflows and require slow algorithm, into the new timehands
th_large_delta member.

This part with overflow fix was written by Bruce Evans.

Reported by:	Mark Millard <marklmi@yahoo.com> (the overflow issue)
Tested by:	pho
Discussed with:	emaste
Sponsored by:	The FreeBSD Foundation (kib)
MFC after:	3 weeks
This commit is contained in:
Konstantin Belousov
2020-02-14 23:27:45 +00:00
committed by Moyano, Gabriel
parent 265c2e620b
commit 166a80c3d8

View File

@@ -152,6 +152,7 @@ struct timehands {
struct timecounter *th_counter; struct timecounter *th_counter;
int64_t th_adjustment; int64_t th_adjustment;
uint64_t th_scale; uint64_t th_scale;
u_int th_large_delta;
uint32_t th_offset_count; uint32_t th_offset_count;
struct bintime th_offset; struct bintime th_offset;
struct bintime th_bintime; struct bintime th_bintime;
@@ -172,6 +173,7 @@ static struct timehands ths[16] = {
[0] = { [0] = {
.th_counter = &dummy_timecounter, .th_counter = &dummy_timecounter,
.th_scale = (uint64_t)-1 / 1000000, .th_scale = (uint64_t)-1 / 1000000,
.th_large_delta = 1000000,
.th_offset = { .sec = 1 }, .th_offset = { .sec = 1 },
.th_generation = 1, .th_generation = 1,
}, },
@@ -342,20 +344,72 @@ tc_delta(struct timehands *th)
* the comment in <sys/time.h> for a description of these 12 functions. * the comment in <sys/time.h> for a description of these 12 functions.
*/ */
#ifdef FFCLOCK static __inline void
void bintime_off(struct bintime *bt, u_int off)
fbclock_binuptime(struct bintime *bt)
{ {
struct timehands *th; struct timehands *th;
unsigned int gen; struct bintime *btp;
uint64_t scale, x;
u_int delta, gen, large_delta;
do { do {
th = timehands; th = timehands;
gen = atomic_load_acq_int(&th->th_generation); gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_offset; btp = (struct bintime *)((vm_offset_t)th + off);
bintime_addx(bt, th->th_scale * tc_delta(th)); *bt = *btp;
scale = th->th_scale;
delta = tc_delta(th);
large_delta = th->th_large_delta;
atomic_thread_fence_acq(); atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation); } while (gen == 0 || gen != th->th_generation);
if (__predict_false(delta >= large_delta)) {
/* Avoid overflow for scale * delta. */
x = (scale >> 32) * delta;
bt->sec += x >> 32;
bintime_addx(bt, x << 32);
bintime_addx(bt, (scale & 0xffffffff) * delta);
} else {
bintime_addx(bt, scale * delta);
}
}
#define GETTHBINTIME(dst, member) \
do { \
_Static_assert(_Generic(((struct timehands *)NULL)->member, \
struct bintime: 1, default: 0) == 1, \
"struct timehands member is not of struct bintime type"); \
bintime_off(dst, __offsetof(struct timehands, member)); \
} while (0)
static __inline void
getthmember(void *out, size_t out_size, u_int off)
{
struct timehands *th;
u_int gen;
do {
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
memcpy(out, (char *)th + off, out_size);
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
}
#define GETTHMEMBER(dst, member) \
do { \
_Static_assert(_Generic(*dst, \
__typeof(((struct timehands *)NULL)->member): 1, \
default: 0) == 1, \
"*dst and struct timehands member have different types"); \
getthmember(dst, sizeof(*dst), __offsetof(struct timehands, \
member)); \
} while (0)
#ifdef FFCLOCK
void
fbclock_binuptime(struct bintime *bt)
{
GETTHBINTIME(bt, th_offset);
} }
void void
@@ -379,16 +433,8 @@ fbclock_microuptime(struct timeval *tvp)
void void
fbclock_bintime(struct bintime *bt) fbclock_bintime(struct bintime *bt)
{ {
struct timehands *th;
unsigned int gen;
do { GETTHBINTIME(bt, th_bintime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_bintime;
bintime_addx(bt, th->th_scale * tc_delta(th));
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
@@ -412,100 +458,55 @@ fbclock_microtime(struct timeval *tvp)
void void
fbclock_getbinuptime(struct bintime *bt) fbclock_getbinuptime(struct bintime *bt)
{ {
struct timehands *th;
unsigned int gen;
do { GETTHMEMBER(bt, th_offset);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_offset;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
fbclock_getnanouptime(struct timespec *tsp) fbclock_getnanouptime(struct timespec *tsp)
{ {
struct timehands *th; struct bintime bt;
unsigned int gen;
do { GETTHMEMBER(&bt, th_offset);
th = timehands; bintime2timespec(&bt, tsp);
gen = atomic_load_acq_int(&th->th_generation);
bintime2timespec(&th->th_offset, tsp);
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
fbclock_getmicrouptime(struct timeval *tvp) fbclock_getmicrouptime(struct timeval *tvp)
{ {
struct timehands *th; struct bintime bt;
unsigned int gen;
do { GETTHMEMBER(&bt, th_offset);
th = timehands; bintime2timeval(&bt, tvp);
gen = atomic_load_acq_int(&th->th_generation);
bintime2timeval(&th->th_offset, tvp);
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
fbclock_getbintime(struct bintime *bt) fbclock_getbintime(struct bintime *bt)
{ {
struct timehands *th;
unsigned int gen;
do { GETTHMEMBER(bt, th_bintime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_bintime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
fbclock_getnanotime(struct timespec *tsp) fbclock_getnanotime(struct timespec *tsp)
{ {
struct timehands *th;
unsigned int gen;
do { GETTHMEMBER(tsp, th_nanotime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*tsp = th->th_nanotime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
fbclock_getmicrotime(struct timeval *tvp) fbclock_getmicrotime(struct timeval *tvp)
{ {
struct timehands *th;
unsigned int gen;
do { GETTHMEMBER(tvp, th_microtime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*tvp = th->th_microtime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
#else /* !FFCLOCK */ #else /* !FFCLOCK */
void void
binuptime(struct bintime *bt) binuptime(struct bintime *bt)
{ {
struct timehands *th;
uint32_t gen;
do { GETTHBINTIME(bt, th_offset);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_offset;
bintime_addx(bt, th->th_scale * tc_delta(th));
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
#ifdef __rtems__ #ifdef __rtems__
sbintime_t sbintime_t
@@ -548,16 +549,8 @@ microuptime(struct timeval *tvp)
void void
bintime(struct bintime *bt) bintime(struct bintime *bt)
{ {
struct timehands *th;
u_int gen;
do { GETTHBINTIME(bt, th_bintime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_bintime;
bintime_addx(bt, th->th_scale * tc_delta(th));
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
@@ -581,85 +574,47 @@ microtime(struct timeval *tvp)
void void
getbinuptime(struct bintime *bt) getbinuptime(struct bintime *bt)
{ {
struct timehands *th;
uint32_t gen;
do { GETTHMEMBER(bt, th_offset);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_offset;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
getnanouptime(struct timespec *tsp) getnanouptime(struct timespec *tsp)
{ {
struct timehands *th; struct bintime bt;
uint32_t gen;
do { GETTHMEMBER(&bt, th_offset);
th = timehands; bintime2timespec(&bt, tsp);
gen = atomic_load_acq_int(&th->th_generation);
bintime2timespec(&th->th_offset, tsp);
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
getmicrouptime(struct timeval *tvp) getmicrouptime(struct timeval *tvp)
{ {
struct timehands *th; struct bintime bt;
uint32_t gen;
do { GETTHMEMBER(&bt, th_offset);
th = timehands; bintime2timeval(&bt, tvp);
gen = atomic_load_acq_int(&th->th_generation);
bintime2timeval(&th->th_offset, tvp);
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
getbintime(struct bintime *bt) getbintime(struct bintime *bt)
{ {
struct timehands *th;
uint32_t gen;
do { GETTHMEMBER(bt, th_bintime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*bt = th->th_bintime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
getnanotime(struct timespec *tsp) getnanotime(struct timespec *tsp)
{ {
struct timehands *th;
uint32_t gen;
do { GETTHMEMBER(tsp, th_nanotime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*tsp = th->th_nanotime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
void void
getmicrotime(struct timeval *tvp) getmicrotime(struct timeval *tvp)
{ {
struct timehands *th;
uint32_t gen;
do { GETTHMEMBER(tvp, th_microtime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*tvp = th->th_microtime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
#endif /* FFCLOCK */ #endif /* FFCLOCK */
@@ -675,15 +630,8 @@ getboottime(struct timeval *boottime)
void void
getboottimebin(struct bintime *boottimebin) getboottimebin(struct bintime *boottimebin)
{ {
struct timehands *th;
u_int gen;
do { GETTHMEMBER(boottimebin, th_boottime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*boottimebin = th->th_boottime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
#ifdef FFCLOCK #ifdef FFCLOCK
@@ -1200,15 +1148,8 @@ getmicrotime(struct timeval *tvp)
void void
dtrace_getnanotime(struct timespec *tsp) dtrace_getnanotime(struct timespec *tsp)
{ {
struct timehands *th;
uint32_t gen;
do { GETTHMEMBER(tsp, th_nanotime);
th = timehands;
gen = atomic_load_acq_int(&th->th_generation);
*tsp = th->th_nanotime;
atomic_thread_fence_acq();
} while (gen == 0 || gen != th->th_generation);
} }
#endif /* __rtems__ */ #endif /* __rtems__ */
@@ -1681,6 +1622,7 @@ _Timecounter_Windup(struct bintime *new_boottimebin,
scale += (th->th_adjustment / 1024) * 2199; scale += (th->th_adjustment / 1024) * 2199;
scale /= th->th_counter->tc_frequency; scale /= th->th_counter->tc_frequency;
th->th_scale = scale * 2; th->th_scale = scale * 2;
th->th_large_delta = MIN(((uint64_t)1 << 63) / scale, UINT_MAX);
/* /*
* Now that the struct timehands is again consistent, set the new * Now that the struct timehands is again consistent, set the new