Skip site navigation (1)Skip section navigation (2)
Date:      Sat, 5 Oct 2019 18:22:20 +1000 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Bruce Evans <brde@optusnet.com.au>
Cc:        Poul-Henning Kamp <phk@phk.freebsd.dk>,  Sebastian Huber <sebastian.huber@embedded-brains.de>,  Warner Losh <imp@bsdimp.com>, Konstantin Belousov <kostikbel@gmail.com>,  FreeBSD <freebsd-hackers@freebsd.org>
Subject:   Re: Why is tc_get_timecount() called two times in tc_init()?
Message-ID:  <20191005171343.X925@besplex.bde.org>
In-Reply-To: <20191005024530.U1757@besplex.bde.org>
References:  <0e27fb3e-0f60-68e1-dbba-f17c3d91c332@embedded-brains.de> <20191002140040.GA44691@kib.kiev.ua> <20191003013314.O2151@besplex.bde.org> <20191002163946.GE44691@kib.kiev.ua> <20191003030837.C2787@besplex.bde.org> <20191003084021.GI44691@kib.kiev.ua> <CANCZdfpcOBJiYAKafhiWZS2g4vnLGVvzqhaOXetSSnU2Hj91nw@mail.gmail.com> <47834.1570116246@critter.freebsd.dk> <141ee0af-2ff4-50fc-b4e4-6d1fc47e04f3@embedded-brains.de> <60167.1570198248@critter.freebsd.dk> <20191005024530.U1757@besplex.bde.org>

next in thread | previous in thread | raw e-mail | index | archive | help
On Sat, 5 Oct 2019, Bruce Evans wrote:

> On Fri, 4 Oct 2019, Poul-Henning Kamp wrote:
>> 
>> As long as the counter can be read atomically and does not roll over
>> in a matter of milliseconds, two reads are not necessary.
>
> The i8254 timecounter rolls over in a matter of microseconds if suitably
> (mis)configured.  E.g., for pcaudio i8254 periodic timer was run at
> 16 kHz so it rolled over every 74 or 75 cycles.  I only used this for
> stress tests.
> ...
> No matter how fast the counter can roll over, 2 reads are only useful
> or needed accidentally.  And they are needed for rollover detection
> for the i8254 timecounter:
>
> XX 	low = inb(TIMER_CNTR0);
> XX 	high = inb(TIMER_CNTR0);
> XX 	count = i8254_max_count - ((high << 8) | low);
> XX 	if (count < i8254_lastcount ||
>
> i8254_lastcount is garbage after only 1 read, but it is used here at the
> start of the rollover detection.  Thus the first read returns garbage
> and also leaves some internal state as garbage, but it will update
> i8254_lastcount in its internal state and that is enough for the second
> read to work correctly.

Oops, actually no warmup is needed for some cases, but if warmup is needed
then 2 calls are little better than 1 for giving it.

The internal state is managed by both i8254_get_timecount() and clkintr().
When clkintr() is not active, no warmup is needed.  Then the first call to
i8254_get_timecount() returns a random number that works as a reference
point for subsequent calls, so everything works if this is used immediately
to set the reference point th_offset.  But when clkintr() is not active,
the internal state is not fully initialized until the next clkintr()...

> XX 	    (!i8254_ticked && (clkintr_pending ||
> XX 	    ((count < 20 || (!(flags & PSL_I) &&
> XX 	    count < i8254_max_count / 2u)) &&
> XX 	    i8254_pending != NULL && i8254_pending(i8254_intsrc))))) {
> XX 		i8254_ticked = 1;
> XX 		i8254_offset += i8254_max_count;
> XX 	}

... since this fails to advance i8254_offset when it should, the next
clkintr() does the advance, so the random reference point returned here
becomes invalid (the time appears to step forward).

> The need for 2 calls is only an optimization, but I don't a better way.

The better way is is to just add a warmup/initialization method and call
that instead of abusing tc_get_timecount().  Warmup is only enough if
the timer is already running.  Then a null warmup may be enough.  The
simplifications in r137037 depend on this.  All attachable timecounters
are assumed to be running all the time, so that making one active doesn't
require starting its timer.  For the i8254 timecounter, there were 2 cases
as above:
- i8254 not used for hardclock(), so not interrupting.  No warmup needed
- i8254 used for hardclock(), so interrupting.  Syncing with its interrupt
   needed but not done.
Now event timers give more cases:
- i8254 used for one-shot timeouts, so interrupting.  Syncing with its
   interrupt needed but not done (programming one-shot timeouts doesn't
   change internal state so leaves it as garbage).  Syncing was done for
   pcaudio by only reprogramming the timeouts in the interrupt handler
   under suitable locks.

A related bug:
- the kern.timecounter.tc.<timecounter>.frequency sysctl gives a random
   number corresponding to the above for timecounters other than the
   active one unless they have not wrapped since they were last read.
   Rollover is only detected for the active timecounter.  The random
   number is fairly predictable if the hardware timecounter is real
   hardware (without complications for interrupt handling...).  Reading
   it twice is useless for fixing this.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20191005171343.X925>