summaryrefslogtreecommitdiffstats
path: root/src/arch/x86/core/rdtsc_timer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/arch/x86/core/rdtsc_timer.c')
-rw-r--r--src/arch/x86/core/rdtsc_timer.c157
1 files changed, 120 insertions, 37 deletions
diff --git a/src/arch/x86/core/rdtsc_timer.c b/src/arch/x86/core/rdtsc_timer.c
index e720a239..ed515150 100644
--- a/src/arch/x86/core/rdtsc_timer.c
+++ b/src/arch/x86/core/rdtsc_timer.c
@@ -29,16 +29,70 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
*
*/
-#include <assert.h>
+#include <string.h>
+#include <errno.h>
#include <ipxe/timer.h>
+#include <ipxe/cpuid.h>
#include <ipxe/pit8254.h>
+/** Number of microseconds to use for TSC calibration */
+#define TSC_CALIBRATE_US 1024
+
+/** TSC increment per microsecond */
+static unsigned long tsc_per_us;
+
+/** Minimum resolution for scaled TSC timer */
+#define TSC_SCALED_HZ 32
+
+/** TSC scale (expressed as a bit shift)
+ *
+ * We use this to avoid the need for 64-bit divsion on 32-bit systems.
+ */
+static unsigned int tsc_scale;
+
+/** Number of timer ticks per scaled TSC increment */
+static unsigned long ticks_per_scaled_tsc;
+
+/** Colour for debug messages */
+#define colour &tsc_per_us
+
+/**
+ * Get raw TSC value
+ *
+ * @ret tsc Raw TSC value
+ */
+static inline __always_inline unsigned long rdtsc_raw ( void ) {
+ unsigned long raw;
+
+ __asm__ __volatile__ ( "rdtsc\n\t" : "=a" ( raw ) : : "edx" );
+ return raw;
+}
+
+/**
+ * Get TSC value, shifted to avoid rollover within a realistic timescale
+ *
+ * @ret tsc Scaled TSC value
+ */
+static inline __always_inline unsigned long rdtsc_scaled ( void ) {
+ unsigned long scaled;
+
+ __asm__ __volatile__ ( "rdtsc\n\t"
+ "shrdl %b1, %%edx, %%eax\n\t"
+ : "=a" ( scaled ) : "c" ( tsc_scale ) : "edx" );
+ return scaled;
+}
+
/**
- * Number of TSC ticks per microsecond
+ * Get current system time in ticks
*
- * This is calibrated on the first use of the timer.
+ * @ret ticks Current time, in ticks
*/
-static unsigned long rdtsc_ticks_per_usec;
+static unsigned long rdtsc_currticks ( void ) {
+ unsigned long scaled;
+
+ scaled = rdtsc_scaled();
+ return ( scaled * ticks_per_scaled_tsc );
+}
/**
* Delay for a fixed number of microseconds
@@ -48,47 +102,76 @@ static unsigned long rdtsc_ticks_per_usec;
static void rdtsc_udelay ( unsigned long usecs ) {
unsigned long start;
unsigned long elapsed;
+ unsigned long threshold;
- /* Sanity guard, since we may divide by this */
- if ( ! usecs )
- usecs = 1;
-
- start = currticks();
- if ( rdtsc_ticks_per_usec ) {
- /* Already calibrated; busy-wait until done */
- do {
- elapsed = ( currticks() - start );
- } while ( elapsed < ( usecs * rdtsc_ticks_per_usec ) );
- } else {
- /* Not yet calibrated; use 8254 PIT and calibrate
- * based on result.
- */
- pit8254_udelay ( usecs );
- elapsed = ( currticks() - start );
- rdtsc_ticks_per_usec = ( elapsed / usecs );
- DBG ( "RDTSC timer calibrated: %ld ticks in %ld usecs "
- "(%ld MHz)\n", elapsed, usecs,
- ( rdtsc_ticks_per_usec << TSC_SHIFT ) );
- }
+ start = rdtsc_raw();
+ threshold = ( usecs * tsc_per_us );
+ do {
+ elapsed = ( rdtsc_raw() - start );
+ } while ( elapsed < threshold );
}
/**
- * Get number of ticks per second
+ * Probe RDTSC timer
*
- * @ret ticks_per_sec Number of ticks per second
+ * @ret rc Return status code
*/
-static unsigned long rdtsc_ticks_per_sec ( void ) {
+static int rdtsc_probe ( void ) {
+ unsigned long before;
+ unsigned long after;
+ unsigned long elapsed;
+ uint32_t apm;
+ uint32_t discard_a;
+ uint32_t discard_b;
+ uint32_t discard_c;
+ int rc;
- /* Calibrate timer, if not already done */
- if ( ! rdtsc_ticks_per_usec )
- udelay ( 1 );
+ /* Check that TSC is invariant */
+ if ( ( rc = cpuid_supported ( CPUID_APM ) ) != 0 ) {
+ DBGC ( colour, "RDTSC cannot determine APM features: %s\n",
+ strerror ( rc ) );
+ return rc;
+ }
+ cpuid ( CPUID_APM, &discard_a, &discard_b, &discard_c, &apm );
+ if ( ! ( apm & CPUID_APM_EDX_TSC_INVARIANT ) ) {
+ DBGC ( colour, "RDTSC has non-invariant TSC (%#08x)\n",
+ apm );
+ return -ENOTTY;
+ }
- /* Sanity check */
- assert ( rdtsc_ticks_per_usec != 0 );
+ /* Calibrate udelay() timer via 8254 PIT */
+ before = rdtsc_raw();
+ pit8254_udelay ( TSC_CALIBRATE_US );
+ after = rdtsc_raw();
+ elapsed = ( after - before );
+ tsc_per_us = ( elapsed / TSC_CALIBRATE_US );
+ if ( ! tsc_per_us ) {
+ DBGC ( colour, "RDTSC has zero TSC per microsecond\n" );
+ return -EIO;
+ }
+
+ /* Calibrate currticks() scaling factor */
+ tsc_scale = 31;
+ ticks_per_scaled_tsc = ( ( 1UL << tsc_scale ) /
+ ( tsc_per_us * ( 1000000 / TICKS_PER_SEC ) ) );
+ while ( ticks_per_scaled_tsc > ( TICKS_PER_SEC / TSC_SCALED_HZ ) ) {
+ tsc_scale--;
+ ticks_per_scaled_tsc >>= 1;
+ }
+ DBGC ( colour, "RDTSC has %ld tsc per us, %ld ticks per 2^%d tsc\n",
+ tsc_per_us, ticks_per_scaled_tsc, tsc_scale );
+ if ( ! ticks_per_scaled_tsc ) {
+ DBGC ( colour, "RDTSC has zero ticks per TSC\n" );
+ return -EIO;
+ }
- return ( rdtsc_ticks_per_usec * 1000 * 1000 );
+ return 0;
}
-PROVIDE_TIMER ( rdtsc, udelay, rdtsc_udelay );
-PROVIDE_TIMER_INLINE ( rdtsc, currticks );
-PROVIDE_TIMER ( rdtsc, ticks_per_sec, rdtsc_ticks_per_sec );
+/** RDTSC timer */
+struct timer rdtsc_timer __timer ( TIMER_PREFERRED ) = {
+ .name = "rdtsc",
+ .probe = rdtsc_probe,
+ .currticks = rdtsc_currticks,
+ .udelay = rdtsc_udelay,
+};