Skip to content

Commit 302f1ee

Browse files
committedJan 26, 2017
[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>
1 parent d37e025 commit 302f1ee

File tree

24 files changed

+355
-304
lines changed

24 files changed

+355
-304
lines changed
 

‎src/arch/arm/include/bits/timer.h

Lines changed: 0 additions & 12 deletions
This file was deleted.

‎src/arch/x86/core/rdtsc_timer.c

Lines changed: 120 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,70 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
2929
*
3030
*/
3131

32-
#include <assert.h>
32+
#include <string.h>
33+
#include <errno.h>
3334
#include <ipxe/timer.h>
35+
#include <ipxe/cpuid.h>
3436
#include <ipxe/pit8254.h>
3537

38+
/** Number of microseconds to use for TSC calibration */
39+
#define TSC_CALIBRATE_US 1024
40+
41+
/** TSC increment per microsecond */
42+
static unsigned long tsc_per_us;
43+
44+
/** Minimum resolution for scaled TSC timer */
45+
#define TSC_SCALED_HZ 32
46+
47+
/** TSC scale (expressed as a bit shift)
48+
*
49+
* We use this to avoid the need for 64-bit divsion on 32-bit systems.
50+
*/
51+
static unsigned int tsc_scale;
52+
53+
/** Number of timer ticks per scaled TSC increment */
54+
static unsigned long ticks_per_scaled_tsc;
55+
56+
/** Colour for debug messages */
57+
#define colour &tsc_per_us
58+
59+
/**
60+
* Get raw TSC value
61+
*
62+
* @ret tsc Raw TSC value
63+
*/
64+
static inline __always_inline unsigned long rdtsc_raw ( void ) {
65+
unsigned long raw;
66+
67+
__asm__ __volatile__ ( "rdtsc\n\t" : "=a" ( raw ) : : "edx" );
68+
return raw;
69+
}
70+
71+
/**
72+
* Get TSC value, shifted to avoid rollover within a realistic timescale
73+
*
74+
* @ret tsc Scaled TSC value
75+
*/
76+
static inline __always_inline unsigned long rdtsc_scaled ( void ) {
77+
unsigned long scaled;
78+
79+
__asm__ __volatile__ ( "rdtsc\n\t"
80+
"shrdl %b1, %%edx, %%eax\n\t"
81+
: "=a" ( scaled ) : "c" ( tsc_scale ) : "edx" );
82+
return scaled;
83+
}
84+
3685
/**
37-
* Number of TSC ticks per microsecond
86+
* Get current system time in ticks
3887
*
39-
* This is calibrated on the first use of the timer.
88+
* @ret ticks Current time, in ticks
4089
*/
41-
static unsigned long rdtsc_ticks_per_usec;
90+
static unsigned long rdtsc_currticks ( void ) {
91+
unsigned long scaled;
92+
93+
scaled = rdtsc_scaled();
94+
return ( scaled * ticks_per_scaled_tsc );
95+
}
4296

4397
/**
4498
* Delay for a fixed number of microseconds
@@ -48,47 +102,76 @@ static unsigned long rdtsc_ticks_per_usec;
48102
static void rdtsc_udelay ( unsigned long usecs ) {
49103
unsigned long start;
50104
unsigned long elapsed;
105+
unsigned long threshold;
51106

52-
/* Sanity guard, since we may divide by this */
53-
if ( ! usecs )
54-
usecs = 1;
55-
56-
start = currticks();
57-
if ( rdtsc_ticks_per_usec ) {
58-
/* Already calibrated; busy-wait until done */
59-
do {
60-
elapsed = ( currticks() - start );
61-
} while ( elapsed < ( usecs * rdtsc_ticks_per_usec ) );
62-
} else {
63-
/* Not yet calibrated; use 8254 PIT and calibrate
64-
* based on result.
65-
*/
66-
pit8254_udelay ( usecs );
67-
elapsed = ( currticks() - start );
68-
rdtsc_ticks_per_usec = ( elapsed / usecs );
69-
DBG ( "RDTSC timer calibrated: %ld ticks in %ld usecs "
70-
"(%ld MHz)\n", elapsed, usecs,
71-
( rdtsc_ticks_per_usec << TSC_SHIFT ) );
72-
}
107+
start = rdtsc_raw();
108+
threshold = ( usecs * tsc_per_us );
109+
do {
110+
elapsed = ( rdtsc_raw() - start );
111+
} while ( elapsed < threshold );
73112
}
74113

75114
/**
76-
* Get number of ticks per second
115+
* Probe RDTSC timer
77116
*
78-
* @ret ticks_per_sec Number of ticks per second
117+
* @ret rc Return status code
79118
*/
80-
static unsigned long rdtsc_ticks_per_sec ( void ) {
119+
static int rdtsc_probe ( void ) {
120+
unsigned long before;
121+
unsigned long after;
122+
unsigned long elapsed;
123+
uint32_t apm;
124+
uint32_t discard_a;
125+
uint32_t discard_b;
126+
uint32_t discard_c;
127+
int rc;
81128

82-
/* Calibrate timer, if not already done */
83-
if ( ! rdtsc_ticks_per_usec )
84-
udelay ( 1 );
129+
/* Check that TSC is invariant */
130+
if ( ( rc = cpuid_supported ( CPUID_APM ) ) != 0 ) {
131+
DBGC ( colour, "RDTSC cannot determine APM features: %s\n",
132+
strerror ( rc ) );
133+
return rc;
134+
}
135+
cpuid ( CPUID_APM, &discard_a, &discard_b, &discard_c, &apm );
136+
if ( ! ( apm & CPUID_APM_EDX_TSC_INVARIANT ) ) {
137+
DBGC ( colour, "RDTSC has non-invariant TSC (%#08x)\n",
138+
apm );
139+
return -ENOTTY;
140+
}
85141

86-
/* Sanity check */
87-
assert ( rdtsc_ticks_per_usec != 0 );
142+
/* Calibrate udelay() timer via 8254 PIT */
143+
before = rdtsc_raw();
144+
pit8254_udelay ( TSC_CALIBRATE_US );
145+
after = rdtsc_raw();
146+
elapsed = ( after - before );
147+
tsc_per_us = ( elapsed / TSC_CALIBRATE_US );
148+
if ( ! tsc_per_us ) {
149+
DBGC ( colour, "RDTSC has zero TSC per microsecond\n" );
150+
return -EIO;
151+
}
152+
153+
/* Calibrate currticks() scaling factor */
154+
tsc_scale = 31;
155+
ticks_per_scaled_tsc = ( ( 1UL << tsc_scale ) /
156+
( tsc_per_us * ( 1000000 / TICKS_PER_SEC ) ) );
157+
while ( ticks_per_scaled_tsc > ( TICKS_PER_SEC / TSC_SCALED_HZ ) ) {
158+
tsc_scale--;
159+
ticks_per_scaled_tsc >>= 1;
160+
}
161+
DBGC ( colour, "RDTSC has %ld tsc per us, %ld ticks per 2^%d tsc\n",
162+
tsc_per_us, ticks_per_scaled_tsc, tsc_scale );
163+
if ( ! ticks_per_scaled_tsc ) {
164+
DBGC ( colour, "RDTSC has zero ticks per TSC\n" );
165+
return -EIO;
166+
}
88167

89-
return ( rdtsc_ticks_per_usec * 1000 * 1000 );
168+
return 0;
90169
}
91170

92-
PROVIDE_TIMER ( rdtsc, udelay, rdtsc_udelay );
93-
PROVIDE_TIMER_INLINE ( rdtsc, currticks );
94-
PROVIDE_TIMER ( rdtsc, ticks_per_sec, rdtsc_ticks_per_sec );
171+
/** RDTSC timer */
172+
struct timer rdtsc_timer __timer ( TIMER_PREFERRED ) = {
173+
.name = "rdtsc",
174+
.probe = rdtsc_probe,
175+
.currticks = rdtsc_currticks,
176+
.udelay = rdtsc_udelay,
177+
};

‎src/arch/x86/include/bios.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
77
#define BDA_EBDA 0x000e
88
#define BDA_EQUIPMENT_WORD 0x0010
99
#define BDA_FBMS 0x0013
10+
#define BDA_TICKS 0x006c
11+
#define BDA_MIDNIGHT 0x0070
1012
#define BDA_REBOOT 0x0072
1113
#define BDA_REBOOT_WARM 0x1234
1214
#define BDA_NUM_DRIVES 0x0075

‎src/arch/x86/include/bits/errfile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
2626
#define ERRFILE_rtc_entropy ( ERRFILE_ARCH | ERRFILE_CORE | 0x000f0000 )
2727
#define ERRFILE_acpipwr ( ERRFILE_ARCH | ERRFILE_CORE | 0x00100000 )
2828
#define ERRFILE_cpuid ( ERRFILE_ARCH | ERRFILE_CORE | 0x00110000 )
29+
#define ERRFILE_rdtsc_timer ( ERRFILE_ARCH | ERRFILE_CORE | 0x00120000 )
2930

3031
#define ERRFILE_bootsector ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00000000 )
3132
#define ERRFILE_bzimage ( ERRFILE_ARCH | ERRFILE_IMAGE | 0x00010000 )

‎src/arch/x86/include/bits/timer.h

Lines changed: 0 additions & 15 deletions
This file was deleted.

‎src/arch/x86/include/ipxe/bios_timer.h

Lines changed: 0 additions & 44 deletions
This file was deleted.

‎src/arch/x86/include/ipxe/cpuid.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ struct x86_features {
5757
/** Get CPU model */
5858
#define CPUID_MODEL 0x80000002UL
5959

60+
/** Get APM information */
61+
#define CPUID_APM 0x80000007UL
62+
63+
/** Invariant TSC */
64+
#define CPUID_APM_EDX_TSC_INVARIANT 0x00000100UL
65+
6066
/**
6167
* Issue CPUID instruction
6268
*

‎src/arch/x86/include/ipxe/rdtsc_timer.h

Lines changed: 0 additions & 39 deletions
This file was deleted.

‎src/arch/x86/interface/pcbios/bios_timer.c

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,18 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
3232
#include <ipxe/timer.h>
3333
#include <realmode.h>
3434
#include <bios.h>
35+
#include <ipxe/pit8254.h>
36+
37+
/** Number of ticks per day
38+
*
39+
* This seems to be the normative value, as used by e.g. SeaBIOS to
40+
* decide when to set the midnight rollover flag.
41+
*/
42+
#define BIOS_TICKS_PER_DAY 0x1800b0
43+
44+
/** Number of ticks per BIOS tick */
45+
#define TICKS_PER_BIOS_TICK \
46+
( ( TICKS_PER_SEC * 60 * 60 * 24 ) / BIOS_TICKS_PER_DAY )
3547

3648
/**
3749
* Get current system time in ticks
@@ -43,7 +55,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
4355
* of calling timeofday BIOS interrupt.
4456
*/
4557
static unsigned long bios_currticks ( void ) {
46-
static int days = 0;
58+
static uint32_t offset;
4759
uint32_t ticks;
4860
uint8_t midnight;
4961

@@ -53,18 +65,25 @@ static unsigned long bios_currticks ( void ) {
5365
"nop\n\t"
5466
"cli\n\t" );
5567

56-
get_real ( ticks, BDA_SEG, 0x006c );
57-
get_real ( midnight, BDA_SEG, 0x0070 );
68+
/* Read current BIOS time of day */
69+
get_real ( ticks, BDA_SEG, BDA_TICKS );
70+
get_real ( midnight, BDA_SEG, BDA_MIDNIGHT );
5871

72+
/* Handle midnight rollover */
5973
if ( midnight ) {
6074
midnight = 0;
61-
put_real ( midnight, BDA_SEG, 0x0070 );
62-
days += 0x1800b0;
75+
put_real ( midnight, BDA_SEG, BDA_MIDNIGHT );
76+
offset += BIOS_TICKS_PER_DAY;
6377
}
78+
ticks += offset;
6479

65-
return ( days + ticks );
80+
/* Convert to timer ticks */
81+
return ( ticks * TICKS_PER_BIOS_TICK );
6682
}
6783

68-
PROVIDE_TIMER_INLINE ( pcbios, udelay );
69-
PROVIDE_TIMER ( pcbios, currticks, bios_currticks );
70-
PROVIDE_TIMER_INLINE ( pcbios, ticks_per_sec );
84+
/** BIOS timer */
85+
struct timer bios_timer __timer ( TIMER_NORMAL ) = {
86+
.name = "bios",
87+
.currticks = bios_currticks,
88+
.udelay = pit8254_udelay,
89+
};

0 commit comments

Comments
 (0)