[time] Allow timer to be selected at runtime

Allow the active timer (providing udelay() and currticks()) to be
selected at runtime based on probing during the INIT_EARLY stage of
initialisation.

TICKS_PER_SEC is now a fixed compile-time constant for all builds, and
is independent of the underlying clock tick rate.  We choose the value
1024 to allow multiplications and divisions on seconds to be converted
to bit shifts.

TICKS_PER_MS is defined as 1, allowing multiplications and divisions
on milliseconds to be omitted entirely.  The 2% inaccuracy in this
definition is negligible when using the standard BIOS timer (running
at around 18.2Hz).

TIMER_RDTSC now checks for a constant TSC before claiming to be a
usable timer.  (This timer can be tested in KVM via the command-line
option "-cpu host,+invtsc".)

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown
2017-01-25 20:59:15 +00:00
parent d37e025b81
commit 302f1eeb80
24 changed files with 369 additions and 318 deletions

View File

@@ -117,7 +117,7 @@ int parse_timeout ( char *text, unsigned long *value ) {
return rc;
/* Convert to a number of timer ticks */
*value = ( ( value_ms * TICKS_PER_SEC ) / 1000 );
*value = ( value_ms * TICKS_PER_MS );
return 0;
}

View File

@@ -23,11 +23,39 @@
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <ipxe/process.h>
#include <ipxe/console.h>
#include <ipxe/keys.h>
#include <ipxe/nap.h>
#include <ipxe/init.h>
#include <ipxe/timer.h>
/** Current timer */
static struct timer *timer;
/**
* Get current system time in ticks
*
* @ret ticks Current time, in ticks
*/
unsigned long currticks ( void ) {
assert ( timer != NULL );
return timer->currticks();
}
/**
* Delay for a fixed number of microseconds
*
* @v usecs Number of microseconds for which to delay
*/
void udelay ( unsigned long usecs ) {
assert ( timer != NULL );
timer->udelay ( usecs );
}
/**
* Delay for a fixed number of milliseconds
@@ -61,3 +89,35 @@ unsigned int sleep ( unsigned int secs ) {
return 0;
}
/**
* Find a working timer
*
*/
static void timer_probe ( void ) {
int rc;
/* Use first working timer */
for_each_table_entry ( timer, TIMERS ) {
if ( ( timer->probe == NULL ) ||
( ( rc = timer->probe() ) == 0 ) ) {
DBGC ( &timer, "TIMER using %s\n", timer->name );
return;
}
DBGC ( &timer, "TIMER could not initialise %s: %s\n",
timer->name, strerror ( rc ) );
}
/* This is a fatal error */
DBGC ( &timer, "TIMER found no working timers!\n" );
while ( 1 ) {}
}
/** Timer initialisation function */
struct init_fn timer_init_fn __init_fn ( INIT_EARLY ) = {
.initialise = timer_probe,
};
/* Drag in timer configuration */
REQUIRING_SYMBOL ( timer_init_fn );
REQUIRE_OBJECT ( config_timer );