powerpc64 head -r344018 stuck sleeping problems: th->th_scale * tc_delta(th) overflows unsigned 64 bits sometimes

Konstantin Belousov kib at freebsd.org
Thu Feb 28 15:08:19 UTC 2019


On Thu, Feb 28, 2019 at 04:55:42PM +0200, Konstantin Belousov wrote:
> On Thu, Feb 28, 2019 at 05:06:23AM -0800, Mark Millard via freebsd-ppc wrote:
> > Basic context:
> > 
> > The code for sleeps of various forms depends
> > on calls to:
> > 
> > static __inline sbintime_t
> > sbinuptime(void)
> > {
> >         struct bintime _bt;
> > 
> >         binuptime(&_bt);
> >         return (bttosbt(_bt));
> > }
> > 
> > and comparisons with the return values, such
> > as checking for timeouts. The upper 32 bits
> > of the unsigned 64 bit result has the seconds
> > and the lower 32 bits has the fraction as
> > a multiplier of 1sec/(2**64).
> > 
> > An observed problem is that later sbinuptime calls
> > sometimes end up with smaller values than earlier
> > ones. (Past lisy freebsd-ppc messages give details.)
> > This makes for problems with checking for timeouts
> > when using later sbinuptime() calls after a timeout
> > was initially detected against an earlier value:
> > 
> > A.0) timercb getting the earlier sbinuptime() value
> > A.1) callout_process using that to detect a timeout,
> > B)   sleepq_timeout checking the timeout again,
> >      using a separate sbinuptime() call.
> > 
> > Some details about example values, overflows, and such follow.
> > 
> > I used the following sort of hacked code to report values when
> > overflows happen:
> > 
> > #if defined(__powerpc64__) && defined(AIM)
> > void
> > binuptime(struct bintime *bt)
> > {
> >         struct timehands *th;
> >         u_int gen;
> > 
> >         struct timecounter *tc; // HACK!!!
> >         u_int tim_cnt, tim_offset, tim_diff; // HACK!!!
> >         uint64_t freq, scale_factor, diff_scaled; // HACK!!!
> > 
> >         do {
> >                 th = timehands;
> >                 tc = th->th_counter; // HACK!!!
> >                 gen = atomic_load_acq_int(&th->th_generation);
> >                 *bt = th->th_offset;
> >                 tim_cnt= tc->tc_get_timecount(tc); // HACK!!! (steps of tc_diff with values recorded)
> >                 tim_offset= th->th_offset_count; // HACK!!!
> >                 tim_diff= (tim_cnt - tim_offset) & tc->tc_counter_mask; // HACK!!!
> >                 scale_factor= th->th_scale; // HACK!!!
> >                 diff_scaled= scale_factor * tim_diff; // HACK!!!
> >                 bintime_addx(bt, diff_scaled); // HACK!!!
> >                 freq= tc->tc_frequency; // HACK!!!
> >                 atomic_thread_fence_acq();
> >         } while (gen == 0 || gen != th->th_generation);
> > 
> >         if (*(volatile uint64_t*)0xc0000000000000f0==0u && (0xffffffffffffffffull/scale_factor)<tim_diff) { // HACK!!!
> >                 *(volatile uint64_t*)0xc0000000000000d0= freq;
> >                 *(volatile uint64_t*)0xc0000000000000d8= scale_factor;
> >                 *(volatile    u_int*)0xc0000000000000e0= tim_offset;
> >                 *(volatile    u_int*)0xc0000000000000e4= tim_cnt;
> >                 *(volatile    u_int*)0xc0000000000000e8= tim_diff;
> >                 *(volatile uint64_t*)0xc0000000000000f0= diff_scaled;
> >                 *(volatile uint64_t*)0xc0000000000000f8= scale_factor*freq;
> >                 __asm__ ("sync");
> >         }
> > }
> > #else
> > . . .
> > #endif
> > 
> > (mtfb() is used to provide the tc->tc_get_timecount(tc)
> > value --but only the lower 32 bits are extracted and
> > returned.)
> > 
> > Basically whenever tim_diff is such that:
> > 
> > (0xffffffffffffffff/scale_factor)<tim_dif
> > 
> > then diff_scaled overflows an unsigned, 64 bit representation,
> > ending up with just the least 64 bits. This truncated value
> > ends up being used in:
> > 
> >                 bintime_addx(bt, diff_scaled);
> > 
> > Observed consistently for tc->tc_frequency:
> > 
> > tc->tc_frequency == 0x1fca055 (i.e., 33333333)
> > 
> > ( tc->tc_counter_mask is 0xfffffffful as well. )
> > 
> > An example observation of diff_scaled having an overflowed
> > value is:
> > 
> > scale_factor            == 0x80da2067ac
> > scale_factor*freq overflows unsigned, 64 bit representation.
> > tim_offset              ==   0x3da0eaeb
> > tim_cnt                 ==   0x42dea3c4
> > tim_diff                ==    0x53db8d9
> > For reference:                0x1fc9d43 == 0xffffffffffffffffull/scale_factor
> > scaled_diff       == 0xA353A5BF3FF780CC (truncated to 64 bits)
> > 
> > So scale_factor * tim_diff leaves diff_scaled truncated to
> > the least significant 64 bits, which does not preserve
> > ordering properties.
> > 
> > Another example:
> > 
> > scale_factor      ==       0x80d95962c0
> > scale_factor*freq == 0xfffffffffd65c9c0
> > tim_offset        ==         0x4d1fb8e2
> > tim_cnt           ==         0x4d1fb8e1
> > tim_diff          ==         0xffffffff
> > For reference:                0x1fca055 == 0xffffffffffffffffull/scale_factor
> > scaled_diff       == 0xD959623F26A69D40 (truncated to 64 bits)
> > 
> > Again the diff_scaled holds a truncated value from
> > scale_factor * tim_diff .
> > 
> > Another example:
> > 
> > scale_factor      ==       0x80da20c940
> > scale_factor*freq overflows unsigned, 64 bit representation.
> > tim_offset        ==         0x9a7f5cdb
> > tim_cnt           ==          0xb26bbd5
> > tim_diff          ==         0x70a75efa
> > For reference:                0x1fc9d41 == 0xffffffffffffffffull/scale_factor
> > scaled_diff       == 0xB3AC715C56AA0880 (truncated to 64 bits)
> > 
> > Again the diff_scaled holds a truncated value from
> > scale_factor * tim_diff .
> > 
> > Note that the scale_factor does vary.
> > 
> 
> Try the following (I did not even booted it).  If worked out, ffclock
> counterpart also needs the patching.
> 
> diff --git a/sys/kern/kern_tc.c b/sys/kern/kern_tc.c
> index 2656fb4d22f..19e81bbf023 100644
> --- a/sys/kern/kern_tc.c
> +++ b/sys/kern/kern_tc.c
> @@ -355,13 +355,20 @@ void
>  binuptime(struct bintime *bt)
>  {
>  	struct timehands *th;
> -	u_int gen;
> +	uint64_t scale;
> +	u_int delta, gen;
>  
>  	do {
>  		th = timehands;
>  		gen = atomic_load_acq_int(&th->th_generation);
>  		*bt = th->th_offset;
> -		bintime_addx(bt, th->th_scale * tc_delta(th));
> +		scale = th->th_scale;
> +		delta = tc_delta(th);
> +		if (fls(scale) + fls(delta) > 63) {
> +			bt->sec += (scale >> 32) * delta;
> +			scale &= UINT_MAX;
> +		}
> +		bintime_addx(bt, scale * delta);
>  		atomic_thread_fence_acq();
>  	} while (gen == 0 || gen != th->th_generation);

Of course I botched the formula, please try this instead:

diff --git a/sys/kern/kern_tc.c b/sys/kern/kern_tc.c
index 2656fb4d22f..fdd4f4f6a52 100644
--- a/sys/kern/kern_tc.c
+++ b/sys/kern/kern_tc.c
@@ -355,13 +355,22 @@ void
 binuptime(struct bintime *bt)
 {
 	struct timehands *th;
-	u_int gen;
+	uint64_t scale, x;
+	u_int delta, gen;
 
 	do {
 		th = timehands;
 		gen = atomic_load_acq_int(&th->th_generation);
 		*bt = th->th_offset;
-		bintime_addx(bt, th->th_scale * tc_delta(th));
+		scale = th->th_scale;
+		delta = tc_delta(th);
+		if (fls(scale) + fls(delta) > 63) {
+			x = (scale >> 32) * delta;
+			scale &= UINT_MAX;
+			bt->sec += x >> 32;
+			bintime_addx(bt, x << 32);
+		}
+		bintime_addx(bt, scale * delta);
 		atomic_thread_fence_acq();
 	} while (gen == 0 || gen != th->th_generation);
 }
@@ -388,13 +397,22 @@ void
 bintime(struct bintime *bt)
 {
 	struct timehands *th;
-	u_int gen;
+	uint64_t scale, x;
+	u_int delta, gen;
 
 	do {
 		th = timehands;
 		gen = atomic_load_acq_int(&th->th_generation);
 		*bt = th->th_bintime;
-		bintime_addx(bt, th->th_scale * tc_delta(th));
+		scale = th->th_scale;
+		delta = tc_delta(th);
+		if (fls(scale) + fls(delta) > 63) {
+			x = (scale >> 32) * delta;
+			scale &= UINT_MAX;
+			bt->sec += x >> 32;
+			bintime_addx(bt, x << 32);
+		}
+		bintime_addx(bt, scale * delta);
 		atomic_thread_fence_acq();
 	} while (gen == 0 || gen != th->th_generation);
 }


More information about the freebsd-hackers mailing list